1. Overview
In this short article, we’ll be looking at why we might see an unknown source in our Java exception stack trace and how can we fix it.
A Java class file contains optional debug information to facilitate debugging. We can choose during compile time if and what all debug information is added to the class files. This’ll determine what debug information is available during the runtime.
Let’s investigate the Java compiler’s help documentation to see the various options available:
javac -help
Usage: javac <options> <source files>
where possible options include:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
The default behaviour of Java’s compiler is to add the lines and source information to the class files which is equivalent to -g:lines,source.
2.1. Compiling with Debug Option
Let’s see what happens when we compile our Java classes with the options above. We have a Main class that intentionally generates a StringIndexOutOfBoundsException.
Depending on the compiling mechanism used we’ll have to specify the compile option accordingly. Here, we’ll use Maven and its compiler plugin to customise the compiler option:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-g:none</arg>
</compilerArgs>
</configuration>
</plugin>
We’ve set -g to none which means no debugging information will be generated for our compiled classes. Running our buggy Main class generates the stack trace where we see unknown sources instead of the line number where the exception occurred.
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end 10, length 5
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3751)
at java.base/java.lang.String.substring(String.java:1907)
at com.baeldung.unknownsourcestacktrace.Main.getShortenedName(Unknown Source)
at com.baeldung.unknownsourcestacktrace.Main.getGreetingMessage(Unknown Source)
at com.baeldung.unknownsourcestacktrace.Main.main(Unknown Source)
Let’s see what does the generated class file contains. We’ll use javap which is the Java class file disassembler to do this:
javap -l -p Main.class
public class com.baeldung.unknownsourcestacktrace.Main {
private static final org.slf4j.Logger logger;
private static final int SHORT_NAME_LIMIT;
public com.baeldung.unknownsourcestacktrace.Main();
public static void main(java.lang.String[]);
private static java.lang.String getGreetingMessage(java.lang.String);
private static java.lang.String getShortenedName(java.lang.String);
static {};
}
It might be difficult to know what debug information we should expect here, so let’s change the compile option and see what happens.
2.3. The Fix
Let’s now change the compile option to -g:lines,vars,source which will put in LineNumberTable, LocalVariableTable and Source information into our class files. It’s also equivalent to just having -g which puts all the debug information:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-g</arg>
</compilerArgs>
</configuration>
</plugin>
Running our buggy Main class again now produces:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end 10, length 5
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3751)
at java.base/java.lang.String.substring(String.java:1907)
at com.baeldung.unknownsourcestacktrace.Main.getShortenedName(Main.java:23)
at com.baeldung.unknownsourcestacktrace.Main.getGreetingMessage(Main.java:19)
at com.baeldung.unknownsourcestacktrace.Main.main(Main.java:15)
Voila, we see the line number information in our stack trace. Let’s see what changed in our class file:
javap -l -p Main
Compiled from "Main.java"
public class com.baeldung.unknownsourcestacktrace.Main {
private static final org.slf4j.Logger logger;
private static final int SHORT_NAME_LIMIT;
public com.baeldung.unknownsourcestacktrace.Main();
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/baeldung/unknownsourcestacktrace/Main;
public static void main(java.lang.String[]);
LineNumberTable:
line 12: 0
line 13: 8
line 15: 14
line 16: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 args [Ljava/lang/String;
8 22 1 user Lcom/baeldung/unknownsourcestacktrace/dto/User;
private static java.lang.String getGreetingMessage(java.lang.String);
LineNumberTable:
line 19: 0
LocalVariableTable:
Start Length Slot Name Signature
0 28 0 name Ljava/lang/String;
private static java.lang.String getShortenedName(java.lang.String);
LineNumberTable:
line 23: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 name Ljava/lang/String;
static {};
LineNumberTable:
line 8: 0
}
Our class file now contains three crucial pieces of information:
- Source, the top header indicating the .java file from which the .class file has been generated. In the context of a stack trace, it provides the class name where the exception occurred.
- LineNumberTable maps the line number in the code that the JVM actually runs to the line number in our source code file. In the context of a stack trace, it provides the line number where the exception occurred. We also need this to be able to use breakpoints in our debuggers.
- LocalVariableTable contains the details to get the value of a local variable. Debuggers may use it to read the value of a local variable. In the context of a stack trace, this doesn’t matter.
3. Conclusion
We are now familiar with the debug information generated by the Java compiler. The way to manipulate them, -g compiler option. We saw how we can do that with the Maven compiler plugin.
So, if we find unknown sources in our stack traces we can investigate our class files to check if the debug information is available or not. Following which we can choose the right compile option based on our build tool to resolve this issue.
As always, the complete code and Maven configurations are available over on GitHub.