Java Compiler

Su Suriya Ravichandran Updated 14 Nov 2026
7 min read ·Lesson 35 of 133

The Java compiler, javac, translates Java source code (.java files) into bytecode (.class files) that the JVM can run. Along the way it checks the program thoroughly: syntax, types, method signatures, access rules, definite assignment of variables, and reachability of code. Most mistakes in a Java program are caught here, before the program ever runs.

What the Compiler Does

Source (.java) → Parse → Analyze (types, names, rules) → Generate bytecode → .class files
  1. Parsing. The source text is broken into tokens (keywords, names, literals, symbols) and arranged into a syntax tree. Missing semicolons, unbalanced braces, and misspelled keywords fail here.
  2. Analysis. Every name is resolved to a variable, method, or class. Every expression is assigned a type and checked against how it is used. Access modifiers, final assignments, checked exceptions, and definite assignment of local variables are verified.
  3. Desugaring. Convenience syntax is rewritten into simpler forms: enhanced for loops become iterator or index loops, autoboxing becomes Integer.valueOf calls, string concatenation becomes optimized concatenation calls, and generics are erased to their bounds.
  4. Code generation. One .class file is written for every class, interface, enum, and record, including nested and anonymous ones.

The compiler does not produce machine code for your CPU. That happens later, at runtime, inside the JVM's JIT compiler.

Compile-Time Errors Versus Runtime Errors

Compile-time errorRuntime error (exception)
Detected byjavacThe JVM while the program runs
WhenBefore the program can run at allOnly when the faulty code actually executes
ExamplesType mismatch, missing semicolon, undefined variable, missing returnDivision by zero, null reference, array index out of range
ResultNo .class file producedProgram stops with a stack trace (unless the exception is caught)
int count = "five";           // compile-time error: incompatible types
int[] a = new int[3];
a[5] = 1;                     // compiles; fails at runtime with ArrayIndexOutOfBoundsException

The compiler checks types and rules it can prove from the code. It cannot know the values that variables will hold when the program runs, so errors that depend on values appear at runtime.

Reading Compiler Error Messages

public class Order {
    public static void main(String[] args) {
        int qty = 3
        double price = 49.99;
        String total = qty * price;
        System.out.println(totl);
    }
}
javac Order.java
Order.java:3: error: ';' expected
        int qty = 3
                   ^
1 error

Each message has the same structure:

FileName.java:LINE: error: DESCRIPTION
    the offending source line
    ^ caret pointing at the position

Notice that only one error was reported. A syntax error stops the compiler during parsing, before it analyzes types. After fixing the semicolon and recompiling:

Order.java:5: error: incompatible types: double cannot be converted to String
        String total = qty * price;
                           ^
Order.java:6: error: cannot find symbol
        System.out.println(totl);
                           ^
  symbol:   variable totl
  location: class Order
2 errors

Now analysis runs and reports every type and name problem it finds. Fix errors from the top down: an early error can cause later, misleading ones, and fixing it often removes several messages at once.

Common messages

MessageUsual cause
';' expectedMissing semicolon, usually at the end of the line above the caret
cannot find symbolMisspelled name, missing import, variable used outside its scope
incompatible typesAssigning or passing a value of the wrong type
missing return statementA non-void method has a path that does not return
variable x might not have been initializedA local variable is read before every path assigns it
unreachable statementCode after return, break, throw, or an infinite loop
class X is public, should be declared in a file named X.javaFile name does not match the public class
unreported exception X; must be caught or declared to be thrownA checked exception is neither caught nor declared with throws
reached end of file while parsingA closing brace } is missing
non-static method cannot be referenced from a static contextCalling an instance method from main without an object

Warnings

Warnings do not stop compilation but point to likely problems. Enable all of them with -Xlint:

import java.util.ArrayList;
import java.util.List;

public class Legacy {
    public static void main(String[] args) {
        List names = new ArrayList();     // raw types: no generic parameter
        names.add("Ada");
    }
}
javac -Xlint:all Legacy.java
Legacy.java:6: warning: [rawtypes] found raw type: List
        List names = new ArrayList();
        ^
  missing type arguments for generic class List<E>
...
Legacy.java:7: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        names.add("Ada");
                 ^

Raw types bypass generic type checking. The fix is List<String> names = new ArrayList<>();. Adding -Werror turns warnings into errors, which many projects enable in their build to keep the codebase warning-free.

Essential javac Options

OptionEffect
-d <dir>Write .class files into <dir>, organized by package
-cp <path> / --class-path <path>Where to find other compiled classes and JARs the code depends on
--release <N>Compile for Java version N: restricts APIs and language features to N and sets the class-file version
-Xlint:allEnable all recommended warnings
-WerrorTreat warnings as errors
-gInclude full debugging information (local variable names)
-parametersKeep method parameter names in the class file, used by some frameworks
-encoding UTF-8Character encoding of the source files
--source-path <path>Where to find other source files to compile as needed

Separating source and output

project/
├── src/
│   └── com/example/shop/
│       ├── App.java
│       └── Cart.java
└── out/
javac -d out src/com/example/shop/*.java
java -cp out com.example.shop.App

-d out writes out/com/example/shop/App.class and Cart.class, creating the package directories automatically. Keeping compiled output out of the source tree means it can be deleted and rebuilt at any time and excluded from version control.

Compiling against a library

javac -cp lib/gson-2.11.0.jar -d out src/com/example/shop/App.java
java  -cp out:lib/gson-2.11.0.jar com.example.shop.App        # macOS/Linux
java  -cp "out;lib/gson-2.11.0.jar" com.example.shop.App      # Windows

The classpath separator is : on macOS and Linux and ; on Windows. The library must be on the classpath both when compiling (so javac can check calls into it) and when running (so the JVM can load its classes). Build tools like Maven and Gradle manage this automatically, which is a major reason real projects use them.

What javac Generates

Each class becomes its own file, including nested and anonymous classes:

public class Shop {
    class Item {}                         // inner class
    enum Size { S, M, L }                 // nested enum
    Runnable task = new Runnable() {      // anonymous class
        public void run() {}
    };
}
javac Shop.java
ls
Shop.class  Shop$Item.class  Shop$Size.class  Shop$1.class  Shop.java

Nested types are named Outer$Inner.class; anonymous classes are numbered Outer$1.class, Outer$2.class, in order of appearance. All of these must be shipped together.

javap -c shows the generated bytecode:

public class Add {
    static int add(int a, int b) {
        return a + b;
    }
}
javap -c Add
  static int add(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: iadd
       3: ireturn

The JVM is a stack machine: iload_0 and iload_1 push the two int parameters onto the operand stack, iadd pops both and pushes their sum, and ireturn returns the top value. The i prefix means int; there are separate instructions for long, double, and references, which is how the JVM's verifier can check type safety of the bytecode itself.

The Compiler and the Language Version

Each JDK's compiler supports the language features of its version. Compiling code that uses a newer feature with an older JDK fails:

record Point(int x, int y) {}   // records: Java 16+

On a Java 11 JDK:

error: class, interface, or enum expected

The message is confusing because the old compiler does not know the word record at all. When code from a tutorial or project does not compile, checking javac -version is a good first step.

Conversely, --release lets a newer JDK produce code for an older runtime:

javac --release 17 -d out src/**/*.java

The compiler then rejects features and APIs not available in Java 17. Prefer --release over the older -source/-target pair, which set the language level and class-file version but do not check API usage, allowing code that compiles successfully and then fails on the older runtime.

The Compiler Inside Other Tools

Build tools and IDEs do not reimplement Java compilation; they call the JDK's compiler through its API (javax.tools.JavaCompiler). Maven's compiler plugin, Gradle's compileJava task, and the java Source.java single-file launcher all use the same javac engine. Eclipse is the main exception, using its own incremental compiler (ECJ), which is why it can show errors instantly while you type in very large projects. The rules enforced are the same, since both implement the Java Language Specification.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Java Compiler and get answers drawn from it.

Signed-in readers only.