How Java Actually Runs
Every UPI transaction you’ve ever made almost certainly passed through Java code somewhere — banks, NPCI-scale payment rails, and most Indian product companies run their backends on it. Before you write a single class, you need to know what actually happens between “I typed code” and “the machine ran it” — because that gap is where 90% of beginner confusion lives. This module closes that gap.
The Goal
By the end of this module you can:
- Explain why companies bet their money movement on Java, in two sentences
- Install JDK 21 and verify it works from a plain terminal
- Compile and run a Java program with
javacandjava— no IDE, and know what each command does - Draw the
.java→.class→ JVM pipeline and say why “write once, run anywhere” is literally true - Read a stack trace line by line and point at the exact line that broke
The Lesson
What Java is, and why companies run on it
Java is a language where the type of everything is checked before the program runs. In JavaScript, amount + fee happily glues "500" + 50 into "50050" and you find out in production. In Java, that’s a compile error — the program refuses to even exist until you fix it.
That’s the core trade: Java is stricter and more verbose, and in exchange a whole category of bugs dies before deployment. For a bank moving crores per minute, that trade is a no-brainer.
| Why companies pick Java | What it means for you |
|---|---|
| Compile-time type checking | Bugs caught before code ships |
| 25+ years of battle-testing | The boring, reliable choice for money systems |
| Runs the same on any OS | Deploy to Linux servers, develop on Windows |
| Massive hiring pool | Java jobs everywhere, especially in India |
| Spring ecosystem | The backend framework most enterprises use |
You know JS. Keep it. Java is your second language, and the contrast between them will teach you more than either alone.
Install JDK 21 and verify
The JDK (Java Development Kit) is the compiler plus the runtime plus tools. Download Eclipse Temurin 21 (a free, production-grade JDK build) or use any JDK 21+ — version 17+ is fine, 21 is the current long-term-support sweet spot.
After installing, open a fresh terminal (PowerShell on Windows) and run both of these:
java -version
javac -version
You need both to print 21-something. They are different tools:
| Command | What it is | What it does |
|---|---|---|
javac | The compiler | Turns your .java source file into a .class bytecode file |
java | The launcher | Starts the JVM and runs a .class file |
If java works but javac doesn’t, you installed a JRE (runtime only), not the JDK. Reinstall the JDK. If neither works, the install didn’t add itself to your PATH — the fix is reinstalling with the “add to PATH” option ticked, then opening a new terminal.
Your first program — no IDE
IDEs hide the pipeline. You’re going to see it raw, once, so it’s never magic again.
Create a folder, then a file named exactly Hello.java (capital H — the filename must match the class name):
public class Hello {
public static void main(String[] args) {
System.out.println("Namaste, JVM");
}
}
Now in the terminal, in that folder:
javac Hello.java
java Hello
Two things just happened:
javac Hello.javaread your source and produced a new file,Hello.class. Look in the folder — it’s there. That file is bytecode: instructions for the JVM, not for your CPU.java Hello(no.classextension — you name the class, not the file) started a JVM, loadedHello.class, and executedmain.
You never run the .java file. You compile it, then run the result.
The pipeline — and why “write once, run anywhere” is real
Here’s the full journey:
flowchart LR
A[Hello.java source code] -->|javac compiles| B[Hello.class bytecode]
B -->|java launches JVM| C[JVM on Windows]
B -->|same file| D[JVM on Linux]
B -->|same file| E[JVM on Mac]
C --> F[Runs]
D --> F
E --> F
The key insight: bytecode is not machine code. Your CPU can’t run it. The JVM — Java Virtual Machine — is a program that reads bytecode and executes it on whatever machine it’s installed on.
This is the “write once, run anywhere” promise. You compile Hello.java once on your Windows laptop. That exact Hello.class file runs unmodified on a Linux server in a Mumbai data centre, because each OS has its own JVM that speaks the same bytecode. In JS terms: bytecode is like your JS source, and the JVM is like Node or the browser engine — the portable layer that makes “any machine” work.
JIT in two lines: The JVM doesn’t just interpret bytecode forever — it watches which code runs hot (a loop processing 10,000 payments) and compiles that part down to native machine code on the fly. That’s the JIT (Just-In-Time) compiler, and it’s why long-running Java servers get faster after warming up.
Stack vs heap — your first look
This is the most important mental picture in all of Java. Memory is split into two regions:
flowchart TD
subgraph Stack[The Stack - one frame per method call]
F1[main frame holds variable account]
F2[printBalance frame holds parameter b]
end
subgraph Heap[The Heap - where objects live]
O1[Account object name Darshan balance 5000]
end
F1 -->|reference points to| O1
F2 -->|same object| O1
- The stack holds method calls. Every time a method runs, it gets a frame — a small box holding its local variables. When the method returns, the frame is destroyed.
maingets a frame; ifmaincallsprintBalance, a second frame stacks on top; whenprintBalancefinishes, its frame pops off. - The heap holds objects. When you write
new Account(...), the object is created on the heap. What sits in your stack frame is just a reference — an arrow pointing at the heap object.
Why you care right now: two variables can point at the same heap object, and a reference can point at nothing — which is null, and the source of Java’s most famous crash. We go deep on this in the next module. For now, burn in the picture: frames on the stack, objects on the heap, arrows between them.
Anatomy of main
That ceremony around your one print line — every word earns its place:
public class Hello { // a class is the container - all Java code lives in one
public static void main(String[] args) { // the exact signature the JVM looks for
System.out.println("Namaste, JVM");
}
}
| Word | Why it’s there |
|---|---|
public | The JVM is outside your class; it needs permission to call in |
static | The JVM calls main before any object exists — static means “no object needed” |
void | main returns nothing to the JVM |
main | The exact name the JVM searches for. Main, mian — won’t run |
String[] args | Command-line arguments, as an array of strings. java Hello hi puts "hi" in args[0] |
Change any of these and you get the classic beginner error: Error: Main method not found. Now you know why.
The beginner superpower — reading a stack trace
When a Java program crashes, it doesn’t just die. It prints a stack trace — a snapshot of the stack at the moment of death. Most beginners panic and paste it into AI without reading it. You’re going to read it.
Here’s a real one:
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.toUpperCase()" because "name" is null
at HelloBank.formatName(HelloBank.java:14)
at HelloBank.printSummary(HelloBank.java:9)
at HelloBank.main(HelloBank.java:4)
Walk it line by line:
- Line 1 — what went wrong.
NullPointerException: a reference pointing at nothing was used. Modern Java even tells you which variable:"name" is null. - Line 2 — the crash site.
at HelloBank.formatName(HelloBank.java:14)— the explosion happened in methodformatName, fileHelloBank.java, line 14. Open that exact line first, always. - Lines 3-4 — how we got there. Read upward through the calls:
main(line 4) calledprintSummary(line 9), which calledformatName(line 14), which died. It’s the stack frames from the diagram above, printed top-of-stack first.
The reading order: first line tells you what, top at line tells you where, the rest tells you the path. The bug is usually at the top at line that’s in your code (skip lines mentioning java.base — that’s library internals).
flowchart TD
A[Program crashes] --> B[Read line 1 - the exception type and message]
B --> C[Find the top at line in YOUR file]
C --> D[Open that file at that exact line number]
D --> E[Read the calls below it to see the path in]
This skill alone puts you ahead of most beginners. They see red text; you see a map with an X on it.
Build This
You’re building HelloBank — an account summary printer — and then you’re going to break it three different ways on purpose.
- Create a folder
hellobank, and inside it a fileHelloBank.java:
public class HelloBank {
public static void main(String[] args) {
String accountHolder = "Darshan";
String accountType = "Savings";
double balance = 12500.50;
int[] lastThreeTxns = {500, -1200, 250}; // negative means money out
printSummary(accountHolder, accountType, balance, lastThreeTxns);
}
static void printSummary(String holder, String type, double balance, int[] txns) {
System.out.println("=== HelloBank Account Summary ===");
System.out.println("Holder : " + holder.toUpperCase());
System.out.println("Type : " + type);
System.out.println("Balance : Rs " + balance);
System.out.println("Last txn: Rs " + txns[2]);
}
}
- Compile and run it. Confirm you see the summary:
javac HelloBank.java
java HelloBank
-
Break 1 — compile error. Delete the semicolon after
double balance = 12500.50. Runjavac HelloBank.javaagain. Read the error: it names the file, the line, and what it expected. Noticejava HelloBankstill runs — it’s running the old.classfrom step 2. That’s a lesson in itself: stale bytecode. Fix the semicolon, recompile. -
Break 2 — NullPointerException. Change
String accountHolder = "Darshan";toString accountHolder = null;. Recompile, run. It compiles fine —nullis legal — but crashes at runtime onholder.toUpperCase(). Read the stack trace using the method above: what line does the topatpoint to? Which variable does the message name? Fix it back. -
Break 3 — ArrayIndexOutOfBoundsException. Change
txns[2]totxns[5]. Recompile, run. The array has 3 slots (indexes 0, 1, 2) and you asked for index 5. Read the trace: it tells you the bad index and the array length. Fix it back. -
Final check: program compiles clean and prints the summary. You’ve now seen the three failure families — compile-time (caught by
javac), and two runtime crashes (caught by the JVM) — and read all three messages instead of fearing them.
Where to Practice
| Resource | What to do there | How long |
|---|---|---|
| dev.java | Read the “Getting Started” and “Running Your First Program” tutorials | 20 min |
| exercism.org Java track | Do “Hello, World!” and “Lasagna” — pure compile-run reps | 30 min |
| HackerRank Java | ”Welcome to Java!” and “Java Stdin and Stdout I” | 20 min |
How to Practice
- Type everything yourself. Never copy-paste code in these modules — your fingers learning syntax is the point.
- Stay in the terminal for this whole module. The IDE comes later, after the pipeline is in your bones.
- Time-box it: if you’re stuck more than 20 minutes, write down the exact error and ask — don’t spiral.
- Break it on purpose. Every program you write, sabotage once and read the error before fixing it. Errors are the curriculum.
Check Yourself
- What does
javacproduce, and what runs that output? - Why can the same
.classfile run on Windows and Linux without recompiling? - What’s the difference between the JDK and a JVM?
- In one line each: what lives on the stack, and what lives on the heap?
- Why does
mainhave to bestatic? - A stack trace’s first line says
NullPointerException. What do you read next, and why? - You fixed a compile error but the program behaves the same as before. What did you forget?
- What does the JIT compiler do, in one sentence?
Answers
javacproduces a.classfile containing bytecode; the JVM (started by thejavacommand) runs it.- Because bytecode isn’t machine code — each OS has its own JVM that reads the same bytecode. The portability lives in the JVM, not your file.
- The JDK is the full developer kit (compiler
javac+ runtime + tools). The JVM is just the engine that executes bytecode — it’s one part of what the JDK ships. - Stack: one frame per method call, holding that method’s local variables. Heap: objects created with
new, pointed at by references from the stack. - The JVM calls
mainbefore any object of your class exists.staticmeans “callable without an object,” so the JVM can enter your program. - The top
atline that’s in your own code — it names the file and exact line where the crash happened. Open that line first. - To recompile.
javaruns the existing.classfile; editing the.javasource does nothing untiljavacruns again. - The JIT watches which bytecode runs frequently and compiles those hot paths to native machine code while the program runs, making long-running servers faster over time.
Explain it out loud: Sit facing an empty chair (or record a voice note) and explain the full journey from typing HelloBank.java to seeing output on screen — compiler, bytecode, JVM, stack frame for main — as if teaching a friend who only knows JavaScript. If you stall anywhere, that’s the section to re-read.
Still Unclear?
Copy-paste any of these into Claude — they’re built to deepen understanding, not to do the work for you:
I compiled Hello.java and got Hello.class. Explain what's inside a .class file
and why my CPU can't run it directly, using a JS developer's mental model
(I know how Node runs JS). Don't write code for me — explain the layers.
Here is a stack trace I generated on purpose: [paste yours].
Walk me through reading it line by line, then quiz me with a different
made-up stack trace and check whether I can find the crash site myself.
Why did Java's designers choose bytecode plus a VM instead of compiling
straight to machine code like C does? What did they trade away, and where
does that trade-off bite in real production systems?
Why AI Can’t Do This For You
AI can write HelloBank.java in one second. But when a stack trace from a real production system lands in front of you, AI doesn’t know your codebase, your data, or what changed last Tuesday — the person who can read the trace finds the bug.
The pipeline knowledge — what’s compiled, what’s stale, which JVM is running — is exactly what you need when “it works on my machine” breaks in deployment. No prompt replaces knowing where to look.
Module done? Add it to today’s tracker