Course contents

How Java runs: interview questions & scenarios

Free12 questions · 13 what-if follow-ups

These are the questions I use to open a Java interview, because how a candidate explains the platform tells me quickly whether they've only written Java or also run it in production. They look like warm-ups, but every one has a follow-up chain that goes down to bytecode, class loaders and the JIT.

Here's how to use this page: read the question, answer it out loud first, then open the model answer. The "What if…" follow-ups are where interviewers find out how deep you really go, so practise those too.

Question 1. ConceptWarm-up

What is the difference between the JDK, the JRE and the JVM?

Tests platform layers, jlink

Show answerHide answer2 follow-ups

Model answer

They're nested: JDK ⊃ JRE ⊃ JVM. The JVM executes bytecode: class loading, verification, the interpreter, the JIT compilers and the garbage collector. The JRE is the JVM plus the standard class library (java.base, java.sql and so on): enough to run a program. The JDK adds the tools to build and diagnose: javac, jar, jshell, jlink, jcmd, jfr. Since Java 11 there's no separate JRE download; you install a JDK or build a trimmed runtime with jlink.

How I’d say it in the interview

"The JVM runs bytecode, the JRE is the JVM plus the class library, and the JDK adds the developer tools like javac. Since Java 11 you don't download a JRE anymore; in containers I build a small runtime with jlink."

Signals depth

Mentioning that the JVM is a specification with several implementations (HotSpot, OpenJ9, GraalVM), and that the separate JRE went away in Java 11.

Red flags

"The JRE compiles the code" or "the JDK is the Java language". Mixing up javac with the JIT.

Follow-ups interviewers ask next

  1. What if I want the smallest possible runtime image for a Docker container?

    Run jdeps --print-module-deps app.jar to list the JDK modules the app uses, then jlink --add-modules <list> --strip-debug --no-header-files --no-man-pages --compress=zip-6 --output runtime. A java.base-only runtime on JDK 21 is under 30 MB, against roughly 300 MB for a full JDK.

    1. What if the app then fails with ClassNotFoundException: java.sql.Connection?

      The image is missing the java.sql module. That happens when the code uses it only through reflection, or jdeps was run with missing dependencies. Add the module to --add-modules and rebuild; it's a good reason to smoke-test the jlinked image in CI.

Question 2. ConceptCore

Is Java compiled or interpreted?

Tests javac, interpreter, JIT

Show answerHide answer1 follow-up

Model answer

Both, at two different times. javac compiles source to bytecode ahead of time. At runtime the JVM starts by interpreting that bytecode and profiling it; methods that get hot are compiled to native machine code by the JIT. HotSpot uses tiered compilation (default since Java 8): tier 0 is the interpreter, tier 3 is C1 with profiling (after roughly 200 invocations), tier 4 is C2 with aggressive optimisation (after roughly 5,000).

How I’d say it in the interview

"Both. javac compiles to bytecode, and the JVM interprets it at first, then the JIT compiles hot methods to machine code: C1 quickly, then C2 with the profile it collected."

Signals depth

Naming C1 and C2, profiling, and deoptimization: compiled code can be thrown away if an assumption breaks, for example when a new subclass is loaded.

Red flags

"Java is interpreted, so it's slow" (1990s answer) or "javac produces machine code".

Follow-ups interviewers ask next

  1. What if a loop runs a billion times inside main, which is only called once?

    Invocation counts never trigger, but HotSpot also counts loop back-edges and compiles the loop with on-stack replacement (OSR), switching to compiled code in the middle of the running method.

Question 3. What does it print?Core

What does this program print?

Tests class initialization, compile-time constants

Show answerHide answer1 follow-up

Model answer

public class Q {
    public static void main(String[] args) {
        System.out.println("start");
        System.out.println(Settings.MAX);
        System.out.println(Settings.BOXED);
    }
}

class Settings {
    static final int MAX = 100;
    static final Integer BOXED = 7;
    static { System.out.println("Settings init"); }
}

It prints start, 100, Settings init, 7. MAX is a compile-time constant (a static final primitive or String initialized with a constant expression), so javac copies 100 into Q's bytecode and Settings isn't touched. BOXED is an Integer, which is never a constant variable, so reading it is a real getstatic that triggers initialization: <clinit> runs the static block first, then the value prints.

How I’d say it in the interview

"start, 100, Settings init, 7. The int constant is inlined by javac so it doesn't initialize the class. The Integer isn't a compile-time constant, so reading it runs the static initializer first."

Signals depth

Knowing the JLS definition of a constant variable, and the practical consequence: change MAX in Settings and recompile only Settings, and Q still prints the old value.

Red flags

Saying the static block runs when the class is "loaded at startup", or that all static fields behave the same.

Follow-ups interviewers ask next

  1. What if MAX were static final int MAX = compute();?

    Then it's no longer a constant expression, so reading MAX triggers initialization and Settings init prints before 100.

Question 4. ConceptWarm-up

Why is main declared public static void, and what happens if you drop static?

Tests launcher, main signature

Show answerHide answer2 follow-ups

Model answer

The launcher looks up main by name and descriptor and calls it. public so it's reachable from outside the class, static because no instance exists at startup, void because the exit status comes from System.exit (or 0 on normal completion, 1 after an uncaught exception), and String[] args for command-line arguments. Drop static and the class still compiles; on Java 21 java prints Error: Main method is not static in class X and exits with 1.

How I’d say it in the interview

"The JVM needs to call it before any object exists, so it's static; it's public so the launcher can reach it; and it's void because exit codes go through System.exit. A wrong signature compiles fine and only fails at launch."

Signals depth

Saying that the check happens in the launcher, not in javac, and knowing the Java 25 change.

Red flags

"Without static it won't compile."

Follow-ups interviewers ask next

  1. What if you run the same non-static main on Java 25?

    It runs. JEP 512 (final in Java 25, preview in 21 to 24) allows instance main methods: the launcher creates the object with the non-private no-argument constructor and calls main, with or without String[] args.

    1. What if the class has both static main(String[]) and an instance main()?

      Java 25 prefers the method with the String[] parameter, so the static main(String[] args) runs.

Question 5. What does it print?Core

What does this print, and what is the exit status?

Tests System.exit, finally, shutdown hooks

Show answerHide answer

Model answer

public class Shutdown {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("C")));
        try {
            System.out.println("A");
            System.exit(0);
        } finally {
            System.out.println("B");
        }
    }
}

It prints A then C, and the status is 0. System.exit starts the shutdown sequence and never returns, so the finally block never runs. Shutdown hooks do run. Remove the System.exit call and you'd see A, B, C: hooks also run on a normal exit.

How I’d say it in the interview

"A, then C. System.exit never returns, so finally is skipped, but the shutdown hook runs. Exit status zero."

Signals depth

Mentioning that Runtime.halt skips hooks too, and that on Unix only the low 8 bits of the status survive (System.exit(256) reports 0).

Red flags

"finally always runs." It doesn't when the JVM stops underneath it.

Question 6. ConceptCore

How does class loading work, and why can't I replace java.lang.String with my own?

Tests class loaders, delegation

Show answerHide answer2 follow-ups

Model answer

Three built-in loaders: bootstrap (native, loads java.base, shows up as null), platform (Java 9+, loads modules like java.sql) and application (class path and module path). They use parent-first delegation: a loader asks its parent before searching itself. A request for java.lang.String always reaches the bootstrap loader, which finds the real one. Since Java 9 it's stricter: the java.lang package belongs to the java.base module, so javac rejects your file with package exists in another module: java.base.

How I’d say it in the interview

"Bootstrap, platform, application, each delegating to its parent first. So core classes are always loaded by the bootstrap loader, and a fake String on the class path never wins."

Signals depth

Knowing that a class's identity is name plus defining loader, and that Java 9 replaced the extension loader with the platform loader.

Red flags

"The JVM loads every class at startup."

Follow-ups interviewers ask next

  1. What if two different loaders both load com.acme.Order?

    You get two distinct runtime types. Passing one where the other is expected fails with ClassCastException: com.acme.Order cannot be cast to com.acme.Order, a classic in app servers and plugin systems.

    1. What if a framework deliberately loads child-first?

      Some web containers do this for web-app classes so each app can bundle its own library versions. They still delegate java.* to the parent, otherwise the platform would break.

Question 7. DebuggingCore

A service built on your laptop fails in production with UnsupportedClassVersionError. What's going on?

Tests class file version

Show answerHide answer1 follow-up

Model answer

Every class file stores a major version: 52 for Java 8, 61 for Java 17, 65 for Java 21. A JVM refuses a newer one: class file version 65.0, this version of the Java Runtime only recognizes class file versions up to 61.0 means Java 21 bytecode on a Java 17 runtime. Fix it by upgrading the runtime, or by compiling with javac --release 17 (or setting the release in Maven/Gradle).

How I’d say it in the interview

"The bytecode is newer than the JVM. 65 is Java 21 and 61 is Java 17, so someone compiled for 21 and deployed on 17. Either upgrade the runtime or build with --release 17."

Signals depth

Preferring --release over -source/-target: --release also checks that you only use APIs that exist in the target version.

Red flags

Reinstalling the IDE, or suggesting to "recompile everything" without naming the version mismatch.

Follow-ups interviewers ask next

  1. What if only one dependency JAR causes it?

    Run javap -v on a class from that JAR and read major version. The library requires a newer Java than you run, so pin an older version of it or upgrade.

Question 8. ConceptSenior

What is inside a .class file, and what is the constant pool for?

Tests class file format, resolution

Show answerHide answer1 follow-up

Model answer

In order: the magic number 0xCAFEBABE, minor and major version, the constant pool, access flags, this class, super class, interfaces, fields, methods (with their Code attributes holding bytecode) and attributes such as SourceFile. The constant pool is a symbol table: UTF-8 strings, class names, NameAndType pairs and field/method references. Bytecode refers to it by index, like invokevirtual #15, so a call names its target symbolically (java/io/PrintStream.println:(Ljava/lang/String;)V) and the JVM resolves it to a real reference at runtime.

How I’d say it in the interview

"CAFEBABE, the version, then the constant pool, which is a symbol table, then the class's fields and methods with their bytecode. Instructions point into the pool by index, and the JVM resolves those symbolic references lazily when the instruction first runs."

Signals depth

Explaining that symbolic references are why you can upgrade a library without recompiling callers, and that javap -v shows the pool.

Red flags

"It contains machine code" or "the constant pool holds final variables".

Follow-ups interviewers ask next

  1. What if a method referenced in the pool doesn't exist at runtime?

    Nothing happens until that instruction executes. Resolution is lazy in HotSpot, so the program fails with NoSuchMethodError only on the code path that calls it. That's the classic symptom of compiling against one library version and running with another.

Question 9. DebuggingSenior

What is the difference between ClassNotFoundException and NoClassDefFoundError?

Tests loading vs linking, failed initialization

Show answerHide answer

Model answer

ClassNotFoundException is a checked exception from explicit loading by name, such as Class.forName("com.acme.Driver") or loader.loadClass(...), when no loader can find it. NoClassDefFoundError is an error thrown when the JVM implicitly needs a class that was present at compile time but can't be provided at runtime. One common cause isn't a missing file at all: if a static initializer throws, the first access gets ExceptionInInitializerError, and every later access gets NoClassDefFoundError: Could not initialize class X.

public class Broken {
    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            try {
                System.out.println(Cfg.VALUE);
            } catch (Throwable t) {
                System.out.println(t);
            }
        }
    }
}

class Cfg {
    static int VALUE = Integer.parseInt("not a number");
}

This prints java.lang.ExceptionInInitializerError, then java.lang.NoClassDefFoundError: Could not initialize class Cfg.

How I’d say it in the interview

"ClassNotFoundException is when I ask for a class by name and nobody has it. NoClassDefFoundError is when compiled code needs a class the runtime can't give it, either because the JAR is missing or because its static initializer already failed."

Signals depth

The failed initialization case, and saying "scroll up to the first ExceptionInInitializerError in the log".

Red flags

Treating them as the same thing, or assuming NoClassDefFoundError always means a missing JAR.

Question 10. ScenarioSenior

Your service is slow for the first minute after every deployment, then fine. Why, and what can you do?

Tests warm-up, JIT, CDS

Show answerHide answer1 follow-up

Model answer

That's JVM warm-up. Classes load and initialize lazily on the first requests, code runs in the interpreter, and C1 and C2 compile hot paths only after thousands of calls, so early latency can be several times the steady state. Options, in the order I'd try them: send warm-up traffic before the readiness probe passes; use a CDS/AppCDS archive to cut class loading time; on JDK 24+ use the AOT cache (JEP 483) from a training run; make sure the container has enough CPU at startup for the JIT compiler threads; and, if startup dominates everything, consider a GraalVM native image, accepting lower peak throughput.

How I’d say it in the interview

"It's warm-up: lazy class loading plus interpreted code until the JIT catches up. I'd gate readiness on a warm-up routine first, then look at CDS or the JDK 24 AOT cache, and check we're not starving the JIT of CPU."

Signals depth

Mentioning CPU limits starving the JIT compiler threads, and measuring with -XX:+PrintCompilation or JFR rather than guessing.

Red flags

"Increase the heap" or "Java is slow" with no mechanism.

Follow-ups interviewers ask next

  1. What if latency spikes again hours later, after a rarely used feature is first hit?

    That code path is loaded, interpreted and compiled for the first time, and it can also invalidate an optimistic assumption (a new subclass of a type C2 assumed had one implementation), causing deoptimization and recompilation of already-hot methods.

Question 11. CodingWarm-up

Write a program that prints the sum of its integer command-line arguments and exits with status 2 on bad input.

Tests args, parsing, exit codes

Show answerHide answer1 follow-up

Model answer

public class Sum {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println("usage: java Sum <int>...");
            System.exit(2);
        }
        long total = 0;
        for (String arg : args) {
            try {
                total += Integer.parseInt(arg.trim());
            } catch (NumberFormatException e) {
                System.err.println("not an integer: " + arg);
                System.exit(2);
            }
        }
        System.out.println(total);
    }
}

java Sum.java 1 2 3 prints 6 and exits 0; java Sum.java 1 x prints the error to standard error and exits 2. Usage and errors go to System.err so scripts can pipe the real output cleanly. A long accumulator avoids overflow when summing many int values.

How I’d say it in the interview

"Check args.length first, parse each argument, send errors to stderr and exit with a non-zero code a script can test. I'd use a long total so large inputs don't overflow."

Signals depth

Separating stdout from stderr, choosing a distinct exit code for usage errors, and noting that args is never null.

Red flags

Checking args == null, or catching the exception and printing a message but still exiting 0.

Follow-ups interviewers ask next

  1. What if the interviewer asks you to avoid System.exit for testability?

    Move the logic into static int run(String[] args, PrintStream out, PrintStream err) that returns the status, and keep main as one line: System.exit(run(args, System.out, System.err));. Tests then call run directly.

Question 12. ConceptSenior

Can you write a Java program without a class or a static main method?

Tests JEP 445, JEP 512, version awareness

Show answerHide answer1 follow-up

Model answer

Yes since Java 25, where JEP 512 (Compact Source Files and Instance Main Methods) is final. A file can contain only void main() { ... }; the compiler wraps it in an implicitly declared final class, and the launcher creates an instance and calls main. It was a preview in Java 21 (JEP 445, "unnamed classes"), 22, 23 and 24, so on Java 21 it needs --enable-preview (for example java --enable-preview --source 21 Hi.java). Java 25 also adds java.lang.IO for IO.println, and compact files automatically import the public classes of java.base.

How I’d say it in the interview

"On Java 25, yes: a compact source file with an instance void main. On Java 21 it's a preview feature, JEP 445, so it only runs with --enable-preview. In production code I'd still write a normal class."

Signals depth

Getting the version story exactly right (preview 21 to 24, final 25) and knowing the launcher's preference order: main(String[]) before main().

Red flags

"Java always needs public static void main" with no version caveat, or claiming it's final in Java 21.

Follow-ups interviewers ask next

  1. What if you try it on Java 21 without the flag?

    Compilation fails with unnamed classes are a preview feature and are disabled by default, and the message tells you to use --enable-preview.

Lessons in How Java runs