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.

2. Class Debug Information

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.12.1</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.12.1</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:

  1. 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.
  2. 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.
  3. 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.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.