Skip to content

Throwing exceptions and errors in Java

Andrew Binstock edited this page Nov 16, 2023 · 6 revisions

Basics

In its simplest form, here's what happens when an error or exception is thrown: the class of error/exception, such as NullPointerException (which is a class in java.lang) is instantiated and initialized. It is then thrown, which is performed by the ATHROW bytecode. As defined in the JVM specification, the ATHROW bytecode checks to see whether there are any catch processors for this exception in the present method or any of the predecessor frames on the JVM stack. If there is one, all intervening frames are removed from the stack and the logic of the catch processing is executed. If none exists, then ATHROW executes the default action (dump a stack trace) and exit the program.

The bytecode for a one-line program that throws an exception is:

0: new #7 // class java/lang/NullPointerException

3: dup

4: invokespecial #9 // Method java/lang/NullPointerException."<init>":()V

7: athrow

which simply instantiates the object, calls the constructor, and then executes ATHROW.

Instantiating NullPointerException requires going up all the superclasses, which starting from the bottom are:

java.lang.RuntimeException -> java.lang.Exception -> java.lang.Throwable -> java.lang.Object

Of these, java.lang.RuntimeException and java.lang.Exception are primarily classes that call methods in java.lang.Throwable. In fact java.lang.RuntimeException is a class with no fields. It contains a variety of constructors, all of which call constructors in java.lang.Exception. Exactly the same is true of java.lang.Exception, which contains a simple cohort of constructors, all of which call metnods in java.lang.Throwable, which is where all the action happens.

java.lang.Throwable

Throwable is the superclass for both exceptions and errors. It consists of five constructors and 13 methods. Its primary responsibility, which is performed in its constructors, is to get and store the data for a stack trace.

It also handles the special case in which a Throwable is being thrown by another Throwable and handles how to reflect that in the stack trace.

That stack trace is an array of java.lang.StackTraceElemententries. These entries are simply collections of fields:

  • String classLoaderName,
  • String moduleName,
  • String moduleVersion,
  • String declaringClass,
  • String methodName,
  • String fileName,
  • int lineNumber

One StackTraceElement is generated for each frame in the JVM stack at the time the throw occurs.

Ultimately, instantiating a NullPointerException results in an empty Throwable that has been recast all the way down to the subclass. Then, NullPointerException's constructor is called. This goes back up to Throwable, which fills in the stack entries in the array of java.lang.StackTraceElements.

A pointer to that filled-in NullPointerException is then placed on the stack and the ATHROW bytecode is executed, as described above.