Java Module 1 – Start here: JDK 25 LTS and your first programs
IDEs, compiler errors and your first debug
Point VS Code or IntelliJ IDEA at JDK 25, read javac errors and stack traces line by line, and find a bug with breakpoints, stepping and jdb.
What you will learn
- Configure VS Code or IntelliJ IDEA to use the same JDK 25 as the terminal
- Read a javac error message, fix it and compile again until the file compiles
- Trace an exception to its cause and to the first line of your own code
- Explain what a NullPointerException message names, with and without javac -g
- Debug a program with breakpoints, stepping and watched variables, in an IDE and in jdb
Before you start
What you’ll learn · Before you start
What you will learn
- Configure VS Code or IntelliJ IDEA to use the same JDK 25 as the terminal
- Read a javac error message, fix it and compile again until the file compiles
- Trace an exception to its cause and to the first line of your own code
- Explain what a NullPointerException message names, with and without javac -g
- Debug a program with breakpoints, stepping and watched variables, in an IDE and in jdb
Before you start
On this page
Java programs go wrong in three ways, and each has its own tool. A compile error: A message from the compiler that the code breaks a rule of the language, with the file, the line and the problem. A file with a compile error does not compile, so nothing of it runs. is the
compiler refusing code that breaks the rules of the language: javac, or an IDE: An integrated development environment: an editor that also compiles, runs and debugs programs, such as IntelliJ IDEA, or VS Code with its Java extensions. while you type, names the file, the
line and the problem, and nothing runs. An
An object that a running program throws when something fails, such as a NumberFormatException. Unless code catches it, the program stops and prints where it happened.
stops a program that compiled but failed while running, and its stack trace: What an uncaught exception prints: its class and message, then the calls that were open when it was thrown, the most recent first, each with its file and line, and any Caused by sections. names the
line where that happened. A program that runs to the end and prints a wrong answer needs a debugger, which pauses
it at a line you choose so that you can look at its variables and run it one line at a time.
Here you set up an IDE (an editor that also compiles, runs and debugs) on the same JDK 25 as your terminal, read real
messages from javac and real stack traces, and then find a wrong average with
breakpoint: A line marked in a debugger. The program pauses just before that line runs, so you can read its variables and go on one line at a time., in an IDE and in a terminal with jdb, the debugger that comes with every JDK.
The IDE and the terminal must use the same JDK 25
Two IDEs are common for Java: Visual Studio Code with Microsoft’s Extension Pack for Java, and IntelliJ IDEA from JetBrains. In both, the Java support runs on a Java runtime of its own, while your code is compiled with a JDK that you choose for each project, so the first job is to make that the same JDK 25 that your terminal uses.
- VS Code: install the Extension Pack for Java, which includes Language Support for Java by Red Hat, Debugger for
Java, Test Runner for Java, Maven for Java and Project Manager for Java, among others. The Red Hat extension embeds
a Java runtime to run its language server; the JDK that compiles and runs your projects is a separate setting. To
choose JDK 25, run Java: Configure Java Runtime from the Command Palette, or list the JDK in the setting
java.configuration.runtimes. - IntelliJ IDEA: Java support is built in. The IDE runs on a bundled runtime that, in JetBrains’ words, “is not sufficient for developing Java applications”. To choose JDK 25, open File | Project Structure | Project Settings | Project and pick the JDK in the SDK list, or choose Download JDK and a vendor and version.
In VS Code, an entry of java.configuration.runtimes maps a Java version to a folder, for example
{ "name": "JavaSE-25", "path": "/path/to/jdk-25", "default": true }, and the entry marked default is used for
source files that are not part of a Maven or Gradle project. For Maven and Gradle projects the JDK version comes from
pom.xml or build.gradle, so that is where to change it. The Red Hat extension’s documentation lists code from Java
1.8 to Java 27 as supported.
Then check that both agree. This program prints the version of the runtime that runs it; run it once in the IDE and
compare its line with java -version in a terminal, as the session does:
// Prints the version of the Java runtime that runs it. Run it in your IDE, then compare with java -version.
void main() {
IO.println("This program runs on Java " + Runtime.version());
} from session import run
# The JDK the terminal finds first, then the version that a program sees: in an IDE the two lines should name the same release.
run("""
java -version
java Version.java
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ java -version openjdk version "25.0.4.1" LTS OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS) OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing) $ java Version.java This program runs on Java 25.0.4.1+1-LTS
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 version_session.py
Both name release 25.0.4.1 here. When the IDE’s run names another release, the IDE and the terminal disagree, and the
classic symptom is error: release version 25 not supported from an older compiler, which the lesson on
installing JDK 25 reproduces.
Checkpoint
Check: One JDK for the IDE and the terminal
A compile error names the file, the line and the broken rule
The diagram follows a source file through the two places where Java can stop a program: the compiler, before anything runs, and the JVM, while it runs. This section is about the first.
Compile errors stop a program before it runs; exceptions stop it while it runs
Text description of the diagram
The diagram runs from top to bottom.
- The source file Bill.java goes to the compiler: javac in a terminal, or the IDE, which compiles as you type.
- If the code breaks a rule of the language, the compiler reports a compile error such as "Bill.java:5: error: ';' expected", with the file, the line and the problem. Nothing runs.
- If it compiles, the compiler writes Bill.class, and java runs the program.
- If something fails while the program runs, it stops with an exception and a stack trace, with lines such as "at Bill.main(Bill.java:6)". Otherwise it runs to the end and produces its output.
Here is a short program with three mistakes, compiled three times: as written, with the first mistake fixed, and with all three fixed.
public class Bill {
public static void main(String[] args) {
int plates = 3;
int price = "120";
int total = plates * price
System.out.println("Total: " + totl);
}
} from session import run
# The same program three times: with three mistakes, with the first one fixed, and with all of them fixed.
run("""
javac -d out step1/Bill.java
javac -d out step2/Bill.java
javac -d out step3/Bill.java
java -cp out Bill
""", expect_failure=["javac -d out step1/Bill.java", "javac -d out step2/Bill.java"]) Output
# JDK in use: openjdk version "25.0.4.1" LTS
$ javac -d out step1/Bill.java
step1/Bill.java:5: error: ';' expected
int total = plates * price
^
1 error
[exit status 1]
$ javac -d out step2/Bill.java
step2/Bill.java:4: error: incompatible types: String cannot be converted to int
int price = "120";
^
step2/Bill.java:6: error: cannot find symbol
System.out.println("Total: " + totl);
^
symbol: variable totl
location: class Bill
2 errors
[exit status 1]
$ javac -d out step3/Bill.java
$ java -cp out Bill
Total: 360
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py
Every message from javac has the same parts:
step1/Bill.java:5: error: ';' expectedgives the file, the line number and what is wrong.- The next line repeats that source line, and the caret
^below it marks the place where the compiler noticed the problem. For a missing semicolon that is the end of the line; sometimes the real mistake is on the line above. - The last line counts the errors.
The first compilation found one error, and the second, after fixing it, found two. OpenJDK’s overview of the compiler names its stages: the parser first turns the file into syntax trees, then Attr resolves every name and type, and Gen writes the class file. A missing semicolon stops the parser, so the other two mistakes were not looked for yet. Step through the three versions of the file, stage by stage:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- step1 does not parse. javac first parses step1/Bill.java into syntax trees. Line 5 has no semicolon, so parsing stops there with one error: ';' expected.
- The rest is not reached. A file that does not parse never reaches the next stage, so the two other mistakes, on lines 4 and 6, are not reported yet.
- step2 parses. With the semicolon added, step2/Bill.java parses, and Attr resolves every name and type in it.
- Two errors appear. Attr finds both remaining mistakes: "120" in quotes is a String, which cannot be stored in an int, and totl is a symbol it cannot find. 2 errors.
- step3 compiles. With 120 and total fixed, step3/Bill.java gets through Attr as well, and javac writes the class file out/Bill.class.
- It runs. java -cp out Bill runs the class and prints Total: 360, three plates at 120 each.
New errors appearing after a fix are therefore normal, not a sign that the fix was wrong. The two new messages:
incompatible types: String cannot be converted to int:"120"in quotes is text, and text cannot be stored in anintvariable. The fix is the number120.cannot find symbolwithsymbol: variable totlandlocation: class Bill: no variable has that name. It is a typing mistake fortotal.
The habit that follows is simple: fix the first error, compile again, and read the next first error. An IDE compiles
as you type and underlines the same problems in the editor, so most of them are fixed before you ever run javac.
Key idea
Fix the first error and compile again: a file that does not parse is never checked for names and types, so the next compilation may well report more errors than the last.
Checkpoint
Check: Reading a compile error
A stack trace lists the open calls, newest first
This program compiles and starts, then stops with an exception:
public class Report {
public static void main(String[] args) {
String[] lines = {"72", "85", "6O", "91"};
System.out.println("Average: " + average(lines));
}
static double average(String[] lines) {
int total = 0;
for (int i = 0; i < lines.length; i++) {
total += parseMark(lines[i], i + 1);
}
return (double) total / lines.length;
}
static int parseMark(String text, int lineNumber) {
try {
return Integer.parseInt(text.strip());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("line " + lineNumber + " is not a mark: " + text, e);
}
}
} from session import run
run("""
javac -d out Report.java
java -cp out Report
""", expect_failure=["java -cp out Report"]) Output
# JDK in use: openjdk version "25.0.4.1" LTS $ javac -d out Report.java $ java -cp out Report Exception in thread "main" java.lang.IllegalArgumentException: line 3 is not a mark: 6O at Report.parseMark(Report.java:19) at Report.average(Report.java:10) at Report.main(Report.java:4) Caused by: java.lang.NumberFormatException: For input string: "6O" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:565) at java.base/java.lang.Integer.parseInt(Integer.java:662) at Report.parseMark(Report.java:17) ... 2 more [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 trace_session.py
Step through the run first: the calls going down from main, and the exception coming back up through them, at the
lines the trace names:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- main calls average. main calls average(lines) on line 4, with the four marks "72", "85", "6O" and "91".
- Two marks parse. On line 10 the loop calls parseMark for each line in turn. "72" and "85" parse, and the total is 157.
- The third mark. The third call gets "6O": a six followed by the letter O, not the digit zero.
- parseInt fails. On line 17, Integer.parseInt throws a NumberFormatException with the message For input string: "6O".
- A clearer exception. The catch block on line 19 throws an IllegalArgumentException, line 3 is not a mark: 6O, with the NumberFormatException attached as its cause.
- Out of average. average has no catch for it, so the exception leaves average at line 10, the line of the call that was still open.
- Out of main. main has no catch either, so the exception leaves main at line 4. Nothing is left to catch it: the JVM prints the stack trace and the program stops.
- The trace, newest first. The trace lists the open calls from the newest to the oldest: parseMark at line 19, average at 10, main at 4. Caused by then shows the NumberFormatException with its own frames.
Then read the trace from the top:
- The first line names the exception and its message: an
IllegalArgumentExceptionsayingline 3 is not a mark: 6O. - The
atlines are the call stack when it was thrown, the most recent call first:parseMarkat line 19 ofReport.java, whichaveragecalled from line 10, whichmaincalled from line 4. Caused byis the exception that started it all.parseMarkcaught aNumberFormatExceptionand threw the clearer exception with the original attached as its cause. The cause’s message,For input string: "6O", shows the real problem: the third mark holds the letter O, not the digit zero.- Its first three frames are inside the JDK (
java.base/…). Skip them to the first frame in your own code,Report.parseMark(Report.java:17), the line that calledInteger.parseInt. ... 2 moremeans that the cause’s remaining two frames are the same as the last two frames of the exception above (averageandmain), so they are not printed twice; the documentation ofThrowable.printStackTracedescribes this shorthand.
So the usual reading order is: the first line for what happened, the last Caused by for why, and the first frame in
your own files for where.
Key idea
A stack trace answers three questions in three places: the first line says what happened, the last Caused by says
why, and the first frame in your own code says where to look.
Checkpoint
Check: Reading a stack trace
A NullPointerException says what was null
A NullPointerException means that code used a reference that pointed to no object. JEP 358 taught the JVM to say
which reference was null, and the JDK 25 documentation of java lists the option behind it,
-XX:+ShowCodeDetailsInExceptionMessages, as enabled by default. How much the message can say depends on how the
class was compiled:
import java.util.Map;
public class Cities {
public static void main(String[] args) {
Map<String, String> capitals = Map.of("Karnataka", "Bengaluru", "Kerala", "Thiruvananthapuram");
String state = "Goa";
String city = capitals.get(state);
System.out.println(state + ": " + city.length() + " letters");
}
} import java.util.Map;
public class Cities2 {
public static void main(String[] args) {
Map<String, String> capitals = Map.of("Karnataka", "Bengaluru", "Kerala", "Thiruvananthapuram");
String state = "Goa";
System.out.println(state + ": " + capitals.get(state).length() + " letters");
}
} from session import run
# The same NullPointerException from the source launcher, from javac's default class file and from javac -g; the
# table of local variable names that only -g writes; and a null that comes straight from a method call.
run("""
java Cities.java
javac -d out Cities.java
java -cp out Cities
javac -g -d out-g Cities.java
java -cp out-g Cities
javap -c -l -cp out Cities | grep LocalVariableTable || echo "(no LocalVariableTable)"
javap -c -l -cp out-g Cities | sed -n '/void main/,$p' | sed -n '/LocalVariableTable/,$p'
java Cities2.java
""", expect_failure=["java Cities.java", "java -cp out Cities", "java -cp out-g Cities", "java Cities2.java"]) Output
# JDK in use: openjdk version "25.0.4.1" LTS
$ java Cities.java
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local3>" is null
at Cities.main(Cities.java:8)
[exit status 1]
$ javac -d out Cities.java
$ java -cp out Cities
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local3>" is null
at Cities.main(Cities.java:8)
[exit status 1]
$ javac -g -d out-g Cities.java
$ java -cp out-g Cities
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "city" is null
at Cities.main(Cities.java:8)
[exit status 1]
$ javap -c -l -cp out Cities | grep LocalVariableTable || echo "(no LocalVariableTable)"
(no LocalVariableTable)
$ javap -c -l -cp out-g Cities | sed -n '/void main/,$p' | sed -n '/LocalVariableTable/,$p'
LocalVariableTable:
Start Length Slot Name Signature
0 43 0 args [Ljava/lang/String;
12 31 1 capitals Ljava/util/Map;
15 28 2 state Ljava/lang/String;
26 17 3 city Ljava/lang/String;
}
$ java Cities2.java
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because the return value of "java.util.Map.get(Object)" is null
at Cities2.main(Cities2.java:7)
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 npe_session.py
capitals.get("Goa")returnednullbecause the map has no such key, andcity.length()then failed. The message is precise:Cannot invoke "String.length()" because "<local3>" is null.<local3>is a slot number, not a name: here the slots areargs0,capitals1,state2 andcity3, as the table at the end of the session shows. By defaultjavacwrites only line numbers and the source file name as debug information: Tables that javac writes into a class file besides the bytecode: line numbers and the source file's name by default, and local variable names only with javac -g. Debuggers and exception messages use them.; local variable names are written only withjavac -g. Without-g, as from the source launcher or a plainjavac, the JVM knows only the number.- Compiled with
-g, the class file holds aLocalVariableTable, whichjavap -c -lprinted, and the message namescity. Cities2.javacallslength()on the result ofgetdirectly, and the message names the method whose result was null,the return value of "java.util.Map.get(Object)", whatever the compiler options.
Security
These messages describe your code: variable names, field names and method names. That is what you want in your own
logs, and what you do not want in front of the users of a service. Log exceptions where only you can read them, and
show users a plain message instead of e.getMessage() or a stack trace.
Checkpoint
Check: Helpful NullPointerException messages
A breakpoint stops the program before its line runs
This program should print the average of five marks, 73.8, but it prints 59.4:
public class Average {
public static void main(String[] args) {
int[] marks = {72, 85, 64, 91, 57};
double average = average(marks);
System.out.println("Average: " + average);
}
static double average(int[] marks) {
int total = 0;
for (int i = 1; i < marks.length; i++) {
total += marks[i];
}
return (double) total / marks.length;
}
} Reading the code may or may not show the bug. A debugger shows it for certain. In an IDE:
- Set a breakpoint on line 11,
total += marks[i];, by clicking in the margin next to the line number (the gutter, in IntelliJ IDEA). In VS Code, F9 does the same on the current line. - Start the program in debug mode: in VS Code press F5, or use the Debug link that the editor shows for
main; in IntelliJ IDEA click the run icon in the gutter next tomainand choose Debug. - The program stops before line 11 runs, and the variables view shows
marks,totalandiwith their values. Heretotalis 0 andiis already 1, somarks[0], the 72, can never be added: the loop starts at the wrong index. - Step to follow it: Step Over runs one line, Step Into enters a method that the line calls, and Step Out runs to the end of the current method. Continue (VS Code) or Resume (IntelliJ IDEA) runs to the next breakpoint.
Step through such a session. The values are the ones the debugger printed at this breakpoint in the jdb session of
the next section:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Set the breakpoint. Click the margin next to line 11, total += marks[i];, or press F9 on it in VS Code. Nothing happens yet: a breakpoint waits for the program to reach its line.
- Start in debug mode. Start the program in debug mode, with F5 in VS Code or Debug in IntelliJ IDEA. The debugger starts the JVM, and main calls average.
- Stopped before line 11. The program reaches line 11 and stops before the line runs. The variables view shows total = 0 and i = 1.
- The bug, seen. i is already 1 on the first pass, so marks[0], the 72, can never be added. The loop starts at the wrong index.
- Continue. Continue runs to the next stop: the same breakpoint on the next pass, where total = 85, which is marks[1], and i = 2.
- Let it finish. Remove the breakpoint and continue, and the program prints Average: 59.4, the wrong answer that the variables have explained.
- The fix. With the loop starting at i = 0, every mark is added, and the program prints Average: 73.8.
Both IDEs also have watch expressions, which they evaluate at every pause (marks[i] or total + marks[i], say),
and conditional breakpoints, which stop only when a condition you type is true, such as i == 4, so a loop of a
million turns can stop at the one that matters.
| To | VS Code | IntelliJ IDEA |
|---|---|---|
| Set a breakpoint | Click the margin, or F9 | Click the gutter |
| Start debugging | F5, or the Debug link for main |
Run icon in the gutter, then Debug |
| Run one line | Step Over, F10 | Step Over |
| Enter a call | Step Into, F11 | Step Into |
| Leave the method | Step Out, Shift+F11 | Step Out |
| Run to the next stop | Continue, F5 | Resume |
| See variables | Variables and Watch views | Variables tab |
| See the call stack | Call Stack view | Frames tab |
Checkpoint
Check: Breakpoints and stepping
jdb gives the same control in a terminal
jdb is the JDK’s command-line debugger: useful on a server or a lab computer with no IDE, and a clear way to see
what an IDE does behind its buttons. The rows of the table become commands: stop at sets a breakpoint, run starts
the program, next, step and step up run one line, enter a call and leave the method, cont runs to the next
stop, locals and print show variables, and where shows the call stack. Its own help describes them:
from session import run
# jdb describes its own commands; these are the ones the lesson uses.
run("""
printf 'help\\nexit\\n' | jdb | grep -E -A1 '^stop '
printf 'help\\nexit\\n' | jdb | grep -E '^(run |next|step|cont|locals|print|where |clear <class id>:)'
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS
$ printf 'help\nexit\n' | jdb | grep -E -A1 '^stop '
stop [go|thread] [<thread_id>] <at|in> <location>
-- set a breakpoint
$ printf 'help\nexit\n' | jdb | grep -E '^(run |next|step|cont|locals|print|where |clear <class id>:)'
run [class [args]] -- start execution of application's main class
where [<thread id> | all] -- dump a thread's stack
print <expr> -- print value of expression
locals -- print all local variables in current stack frame
clear <class id>:<line> -- clear a breakpoint at a line
step -- execute current line
step up -- execute until the current method returns to its caller
stepi -- execute current instruction
next -- step one line (step OVER calls)
cont -- continue execution from breakpoint
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 jdb_help_session.py
Here is the bug hunt, typed into jdb:
from jdb_session import jdb
from session import run
# The wrong average, then jdb: stop inside the loop, look at the variables, step, and find where the loop started.
run("""
javac -g -d out buggy/Average.java
java -cp out Average
""")
jdb([
"stop at Average:11",
"run",
"locals",
"cont",
"print total",
"print i",
"where",
"clear Average:11",
"cont",
], ["-classpath", "out", "Average"])
run("""
javac -g -d out fixed/Average.java
java -cp out Average
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ javac -g -d out buggy/Average.java $ java -cp out Average Average: 59.4 $ jdb -classpath out Average Initializing jdb ... > stop at Average:11 Deferring breakpoint Average:11. It will be set after the class is loaded. > run run Average Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Set deferred breakpoint Average:11 Breakpoint hit: "thread=main", Average.average(), line=11 bci=10 main[1] locals Method arguments: marks = instance of int[5] (id=432) Local variables: total = 0 i = 1 main[1] cont > Breakpoint hit: "thread=main", Average.average(), line=11 bci=10 main[1] print total total = 85 main[1] print i i = 2 main[1] where [1] Average.average (Average.java:11) [2] Average.main (Average.java:4) main[1] clear Average:11 Removed: breakpoint Average:11 main[1] cont > Average: 59.4 The application exited # JDK in use: openjdk version "25.0.4.1" LTS $ javac -g -d out fixed/Average.java $ java -cp out Average Average: 73.8
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 debug_session.py
javac -gcompiled the class with its debug information, which a debugger needs to show variables by name.stop at Average:11set the breakpoint. The class was not loaded yet, so jdb deferred it until it was.runstarted the program in a second JVM, connected to jdb, and the breakpoint stopped it:Breakpoint hitwith the thread, the method and the line. The prompt changed tomain[1], the stopped thread.localsshowedtotal = 0andi = 1on the very first pass: the bug, found without guessing.contran to the next pass, whereprint totalshowed 85, which ismarks[1], andwhereshowed thataveragehad been called from line 4 ofmain.clear Average:11removed the breakpoint andcontlet the program finish. After the fix,for (int i = 0; ...), the program printed 73.8.
Without -g the debugger can still stop at a line, but it cannot show any local variable:
from jdb_session import jdb
from session import run
# The same class compiled without -g: jdb still stops at the line, but it cannot show the local variables.
run("""
javac -d out-plain buggy/Average.java
""")
jdb([
"stop at Average:11",
"run",
"locals",
"print total",
"clear Average:11",
"cont",
], ["-classpath", "out-plain", "Average"]) Output
# JDK in use: openjdk version "25.0.4.1" LTS $ javac -d out-plain buggy/Average.java $ jdb -classpath out-plain Average Initializing jdb ... > stop at Average:11 Deferring breakpoint Average:11. It will be set after the class is loaded. > run run Average Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Set deferred breakpoint Average:11 Breakpoint hit: "thread=main", Average.average(), line=11 bci=10 main[1] locals Local variable information not available. Compile with -g to generate variable information main[1] print total com.sun.tools.example.debug.expr.ParseException: Name unknown: total total = null main[1] clear Average:11 Removed: breakpoint Average:11 main[1] cont > Average: 59.4 The application exited
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 nodebug_session.py
jdb said so itself: Local variable information not available. Compile with -g to generate variable information. It
is the same debug information that let the NullPointerException name city, so compile with -g whenever you
expect to debug.
Checkpoint
Check: Debugging in a terminal with jdb
Practise: fix five programs
Each of these five programs has one mistake. Fix them so that each one compiles, runs without an exception and prints exactly its expected output:
Fix1.javaprintsWelcome to Kochi.Fix2.javaprintsSeats left: 2.Fix3.javaprintsTotal: 165.Fix4.javaprints47 passes: true.Fix5.javaprints1: 72,2: 85and3: 64, on three lines.
Java (Fix1.java) · exercise/Fix1.java
// 1. Prints a welcome line. It does not compile yet.
void main() {
String city = "Kochi"
IO.println("Welcome to " + city);
} Java (Fix2.java) · exercise/Fix2.java
// 2. Prints how many seats are left. It does not compile yet.
void main() {
int booked = 38;
int capacity = "40";
IO.println("Seats left: " + (capacity - booked));
} Java (Fix3.java) · exercise/Fix3.java
// 3. Prints a bill total. It does not compile yet.
void main() {
int total = 120 + 45;
IO.prinltn("Total: " + total);
} Java (Fix4.java) · exercise/Fix4.java
// 4. Says whether a mark passes. It does not compile yet.
void main() {
IO.println("47 passes: " + passes(47));
}
boolean passes(int mark) {
if (mark >= 35) {
return true;
}
} Java (Fix5.java) · exercise/Fix5.java
// 5. Prints each mark with its position. It compiles, but it stops with an exception.
void main() {
int[] marks = {72, 85, 64};
for (int i = 0; i <= marks.length; i++) {
IO.println((i + 1) + ": " + marks[i]);
}
} The session runs a sample test for each: the program must exit without an error (&& runs the next command only
then), and diff must find no difference from the expected output. As given, all five fail, and the messages above
each [exit status 1] are the place to start:
from session import run
# The sample tests: each program must run without an error and print exactly its expected output.
TESTS = """
java exercise/Fix1.java > got.txt && diff exercise/expected-1.txt got.txt && echo PASS
java exercise/Fix2.java > got.txt && diff exercise/expected-2.txt got.txt && echo PASS
java exercise/Fix3.java > got.txt && diff exercise/expected-3.txt got.txt && echo PASS
java exercise/Fix4.java > got.txt && diff exercise/expected-4.txt got.txt && echo PASS
java exercise/Fix5.java > got.txt && diff exercise/expected-5.txt got.txt && echo PASS
"""
run(TESTS, expect_failure=[line.strip() for line in TESTS.strip().splitlines()]) Output
# JDK in use: openjdk version "25.0.4.1" LTS
$ java exercise/Fix1.java > got.txt && diff exercise/expected-1.txt got.txt && echo PASS
exercise/Fix1.java:3: error: ';' expected
String city = "Kochi"
^
1 error
error: compilation failed
[exit status 1]
$ java exercise/Fix2.java > got.txt && diff exercise/expected-2.txt got.txt && echo PASS
exercise/Fix2.java:4: error: incompatible types: String cannot be converted to int
int capacity = "40";
^
1 error
error: compilation failed
[exit status 1]
$ java exercise/Fix3.java > got.txt && diff exercise/expected-3.txt got.txt && echo PASS
exercise/Fix3.java:4: error: cannot find symbol
IO.prinltn("Total: " + total);
^
symbol: method prinltn(String)
location: class IO
1 error
error: compilation failed
[exit status 1]
$ java exercise/Fix4.java > got.txt && diff exercise/expected-4.txt got.txt && echo PASS
exercise/Fix4.java:10: error: missing return statement
}
^
1 error
error: compilation failed
[exit status 1]
$ java exercise/Fix5.java > got.txt && diff exercise/expected-5.txt got.txt && echo PASS
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at Fix5.main(Fix5.java:5)
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py
First hint: read only the first line of each message, the file, the line number and the problem. For Fix4.java, ask
what passes returns when the mark is below 35. For Fix5.java, the exception names the index that was used and the
length of the array; compare them with the loop’s condition.
With all five fixed, go over the six ideas of the lesson once more. The questions start where you are weakest, and each one shows why its answers are right or wrong as soon as you choose:
Practice that adapts to you
Practise this lesson
12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · How do you read a Java stack trace to find the cause of an exception?
Start at the first line: it names the exception class and its message, which often states the problem outright. The
at lines below it are the call stack at the moment of the throw, the most recent call first, each with the class,
the method, the file and the line number. If there is a Caused by: section, the last one is usually the root cause,
because code often catches a low-level exception and throws a clearer one with the original attached. Skip the frames
inside the JDK or a library and find the first frame in your own code: that line is where to look. ... 2 more means
the remaining frames are the same as in the trace above. A good answer adds that an exception message can be null,
and that NullPointerException messages show a local variable’s name only when the class was compiled with
javac -g.
Key takeaways
- Point the IDE at the same JDK 25 as the terminal: Java: Configure Java Runtime or
java.configuration.runtimesin VS Code, the project SDK in IntelliJ IDEA. - A
javacmessage gives the file, the line, the problem and a caret; fix the first one and compile again, because more errors can appear once the file parses. - Read a stack trace top-down: what happened, the last
Caused byfor why, the first frame in your own code for where. - Helpful
NullPointerExceptionmessages name the null expression; local variables appear by name only in classes compiled withjavac -g. - A breakpoint pauses the program before a line runs; step over, into or out, and read the variables instead of guessing.
jdbgives the same control in a terminal:stop at,run,locals,print,next,step,where,cont.
Terms in this lesson
- breakpoint
- A line marked in a debugger. The program pauses just before that line runs, so you can read its variables and go on one line at a time.
- compile error
- A message from the compiler that the code breaks a rule of the language, with the file, the line and the problem. A file with a compile error does not compile, so nothing of it runs.
- debug information
- Tables that javac writes into a class file besides the bytecode: line numbers and the source file's name by default, and local variable names only with javac -g. Debuggers and exception messages use them.
- IDE
- An integrated development environment: an editor that also compiles, runs and debugs programs, such as IntelliJ IDEA, or VS Code with its Java extensions.
- stack trace
- What an uncaught exception prints: its class and message, then the calls that were open when it was thrown, the most recent first, each with its file and line, and any Caused by sections.
References
- Getting Started with Java in VS Code (Microsoft)
- Java project management in VS Code: Configure Runtime for Projects (Microsoft)
- Language Support for Java by Red Hat (README) (Red Hat)
- Debug code in Visual Studio Code (Microsoft)
- IntelliJ IDEA: SDKs (JetBrains)
- IntelliJ IDEA: Debug code (JetBrains)
- IntelliJ IDEA: Breakpoints (JetBrains)
- IntelliJ IDEA: Start the debugger session (JetBrains)
- JEP 358: Helpful NullPointerExceptions (OpenJDK)
- The java Command (JDK 25 tool reference) (Oracle)
- The jdb Command (JDK 25 tool reference) (Oracle)
- Java SE 25 API: Throwable.printStackTrace (Oracle)
- The javac Command (JDK 25 tool reference) (Oracle)
- Compilation Overview (the stages of javac) (OpenJDK)
- Java SE 25 API: Runtime.version() (Oracle)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress