Back to Class Loading & Compilation
Q3Question 3 of 4

What is the Java compilation and execution flow?

Key Concepts
  • Write .java source → javac compiles to .class bytecode → java runs on JVM
  • javac checks syntax, types, and produces platform-neutral bytecode
  • JVM loads, verifies, and executes bytecode via interpreter + JIT compiler
  • Bytecode is the portable layer – the same .class runs on any JVM
  • Build tools (Maven/Gradle) automate compilation, dependency resolution, and packaging

Definition

Write .java, compile with javac to .class bytecode, run with java on the JVM. The JVM loads, verifies, interprets, and JIT-compiles the bytecode.

Three phases:

  • Write: Source code in .java files.
  • Compile: javac checks syntax and types, produces .class bytecode.
  • Run: java starts the JVM, which loads classes, verifies bytecode, interprets, and JIT-compiles hot paths.

Writing a recipe (source), translating it into a universal format (bytecode), and having a kitchen in any country (JVM) cook the meal.

Understanding the Concepts

The flow

BASH
# 1. Write source
vi TransferService.java

# 2. Compile: javac checks syntax + types, produces bytecode
javac TransferService.java  # produces TransferService.class

# 3. Run: JVM loads, verifies, interprets, JIT-compiles
java TransferService

What javac does

  • Lexical analysis (tokenize), parsing (AST), type checking, annotation processing.
  • Produces .class files with bytecode, not native code.
  • Does NOT optimize much – real optimization happens in the JIT at runtime.

What the JVM does at runtime

  1. Class loading: Reads .class files lazily on first use.
  2. Bytecode verification: Checks safety (no forged references, valid stack).
  3. Interpretation: Runs bytecode instruction by instruction.
  4. JIT compilation: Compiles hot methods to native machine code.
  5. Garbage collection: Reclaims unreachable objects.

Build tools

BASH
# Maven: compile, test, package in one command
mvn clean package

# Gradle
gradle build

Build tools resolve dependencies, compile, run tests, and package into JAR/WAR.

What should a senior engineer know?

  • Ahead-of-time (AOT) compilation with GraalVM native-image compiles to native binary at build time.
  • javac is not the only compiler. Eclipse uses its own compiler (ecj). Kotlin uses kotlinc. All produce JVM bytecode.
  • Incremental compilation (Gradle/IDE) recompiles only changed files.

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

Visual Overview

DIAGRAM
  .java source → javac → .class bytecode → java/JVM
    │                                          │
    │ syntax check, type check                 │ class loading
    │ annotation processing                    │ bytecode verification
    │ → bytecode (platform-neutral)            │ interpreter → JIT → native code
    │                                          │ garbage collection
    ▼                                          ▼
  Build time                              Runtime

Key Takeaway

Java's flow is: write .java, compile with javac to platform-neutral .class bytecode, run on the JVM which loads, verifies, interprets, and JIT-compiles. Build tools like Maven and Gradle automate the entire pipeline.

One sentence to remember: javac produces portable bytecode, the JVM turns it into native code at runtime via interpretation and JIT compilation.

---

Share
Use ← → arrow keys to navigate