Compile errors vs runtime exceptions
Java checks your program twice. First javac, the compiler, reads the whole source file and refuses to produce bytecode if anything is wrong with the grammar or the types. Then the JVM runs the bytecode, and problems that only show up with real data — a null, an index past the end, a division by zero — are thrown as exceptions. The two look different, and knowing which one you have tells you where to look.
Compile error (javac)
- Nothing runs at all — not even the first line of
main - Format:
Main.java:4: error: message, the source line, a^caret - Ends with
1 error(anderror: compilation failedwithjava Main.java) - Always reproducible: same code, same error
Runtime exception (JVM)
- The program started, printed some output, then stopped
- Format:
Exception in thread "main" java.lang.XxxException: message - Followed by
at Class.method(File.java:line)frames - Depends on the input: it may work for one value and crash for another
Anatomy of a javac error
Main.java:4: error: cannot find symbol
System.out.println(totl);
^
symbol: variable totl
location: class Main
1 error
error: compilation failedFile and line (Main.java:4), the kind of error, the offending line with a caret under the exact token, extra detail lines, and the error count.
- Fix the first error first. One missing brace can produce ten follow-on errors; after fixing the first, recompile and many of the rest disappear.
- Trust the line, then look one line up. For a missing
;or), javac often notices on the next token, so the real mistake is at the end of the previous line. - Read the detail lines.
symbol:,location:,required:andfound:usually say exactly what is wrong.
Anatomy of a stack trace
Exception in thread "main" java.lang.NumberFormatException: For input string: "x"
at java.base/java.lang.NumberFormatException.forInputString(Unknown Source)
at java.base/java.lang.Integer.parseInt(Unknown Source)
at java.base/java.lang.Integer.parseInt(Unknown Source)
at Main.parse(Main.java:3)
at Main.total(Main.java:8)
at Main.main(Main.java:13)The real trace from the program at the end of this lesson, without its try/catch. Line 1: the exception type and message. Then the call stack, newest call on top. The bottom frame is where the program started.
- 1Read the first line
The exception class and its message are often the whole answer: For input string: "x" means someone passed
"x"toparseInt. - 2Skip the JDK frames
Frames starting with
java.base/are inside the JDK. The JDK is almost never the bug. - 3Find the first frame in your code
Main.parse(Main.java:3)is where your code made the call that failed. Open that line. - 4Read downward to see how you got there
Each frame below is the caller:
totalline 8 calledparse, andmainline 13 calledtotal. The bad value came in through that path. - 5If there is a "Caused by", jump to the last one
Frameworks wrap exceptions. The deepest
Caused by:section is the original failure, and its first frame in your code is where to start.
A stack trace is also an object you can inspect. This program catches the exception and prints only the frames that belong to Main, which is exactly the filter your eyes should apply:
public class Main {
static int parse(String s) {
return Integer.parseInt(s);
}
static int total(String[] items) {
int t = 0;
for (String s : items) t += parse(s);
return t;
}
public static void main(String[] args) {
try {
total(new String[] {"4", "x"});
} catch (NumberFormatException e) {
System.out.println(e);
for (StackTraceElement f : e.getStackTrace()) {
if (f.getClassName().equals("Main")) {
System.out.println(" at " + f.getMethodName() + " line " + f.getLineNumber());
}
}
}
}
}java.lang.NumberFormatException: For input string: "x"
at parse line 3
at total line 8
at main line 13Move the bad value to the first position ({"x", "4"}). Do the frames change? Why not?
