Career OS

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 javac and java — 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 JavaWhat it means for you
Compile-time type checkingBugs caught before code ships
25+ years of battle-testingThe boring, reliable choice for money systems
Runs the same on any OSDeploy to Linux servers, develop on Windows
Massive hiring poolJava jobs everywhere, especially in India
Spring ecosystemThe 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:

CommandWhat it isWhat it does
javacThe compilerTurns your .java source file into a .class bytecode file
javaThe launcherStarts 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:

  1. javac Hello.java read 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.
  2. java Hello (no .class extension — you name the class, not the file) started a JVM, loaded Hello.class, and executed main.

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. main gets a frame; if main calls printBalance, a second frame stacks on top; when printBalance finishes, 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");
    }
}
WordWhy it’s there
publicThe JVM is outside your class; it needs permission to call in
staticThe JVM calls main before any object exists — static means “no object needed”
voidmain returns nothing to the JVM
mainThe exact name the JVM searches for. Main, mian — won’t run
String[] argsCommand-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:

  1. Line 1 — what went wrong. NullPointerException: a reference pointing at nothing was used. Modern Java even tells you which variable: "name" is null.
  2. Line 2 — the crash site. at HelloBank.formatName(HelloBank.java:14) — the explosion happened in method formatName, file HelloBank.java, line 14. Open that exact line first, always.
  3. Lines 3-4 — how we got there. Read upward through the calls: main (line 4) called printSummary (line 9), which called formatName (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.

  1. Create a folder hellobank, and inside it a file HelloBank.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]);
    }
}
  1. Compile and run it. Confirm you see the summary:
javac HelloBank.java
java HelloBank
  1. Break 1 — compile error. Delete the semicolon after double balance = 12500.50. Run javac HelloBank.java again. Read the error: it names the file, the line, and what it expected. Notice java HelloBank still runs — it’s running the old .class from step 2. That’s a lesson in itself: stale bytecode. Fix the semicolon, recompile.

  2. Break 2 — NullPointerException. Change String accountHolder = "Darshan"; to String accountHolder = null;. Recompile, run. It compiles fine — null is legal — but crashes at runtime on holder.toUpperCase(). Read the stack trace using the method above: what line does the top at point to? Which variable does the message name? Fix it back.

  3. Break 3 — ArrayIndexOutOfBoundsException. Change txns[2] to txns[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.

  4. 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

ResourceWhat to do thereHow long
dev.javaRead the “Getting Started” and “Running Your First Program” tutorials20 min
exercism.org Java trackDo “Hello, World!” and “Lasagna” — pure compile-run reps30 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

  1. What does javac produce, and what runs that output?
  2. Why can the same .class file run on Windows and Linux without recompiling?
  3. What’s the difference between the JDK and a JVM?
  4. In one line each: what lives on the stack, and what lives on the heap?
  5. Why does main have to be static?
  6. A stack trace’s first line says NullPointerException. What do you read next, and why?
  7. You fixed a compile error but the program behaves the same as before. What did you forget?
  8. What does the JIT compiler do, in one sentence?
Answers
  1. javac produces a .class file containing bytecode; the JVM (started by the java command) runs it.
  2. 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.
  3. 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.
  4. Stack: one frame per method call, holding that method’s local variables. Heap: objects created with new, pointed at by references from the stack.
  5. The JVM calls main before any object of your class exists. static means “callable without an object,” so the JVM can enter your program.
  6. The top at line that’s in your own code — it names the file and exact line where the crash happened. Open that line first.
  7. To recompile. java runs the existing .class file; editing the .java source does nothing until javac runs again.
  8. 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

Saves your progress on this device.