Checked vs Unchecked Exceptions in Java

Introduction When something goes wrong while a Java program runs - a file is missing, a number cannot be parsed, an index is past the end of an array - Java signals it with an exception. Java divides exceptions into two kinds, checked and unchecked, and treats them very differently at compile time. This tutorial explains checked vs unchecked exceptions in Java. We will start with what an exception is and what happens when one is not handled. We will then see what makes an exception checked, and the compiler's catch or declare rule. Next we will look at unchecked exceptions and the common ones you will meet. Lastly, we will compare the two side by side, and see when to use each kind for your own exceptions.

(Note - The class hierarchy behind exceptions - Throwable, Error, Exception and RuntimeException - is explained in detail in Understanding the Exception Hierarchy in JavaClick to read tutorial on the Java exception hierarchy.) What is an exception? An exception is an object which describes an unusual event that stops the normal flow of a program. When it happens, Java throws the exception: the current method stops, and Java looks back through the call stackClick to read tutorial: Java Methods: Defining, Calling and Returning Values for code that catches it. If no method catches it, the thread ends, and Java prints the exception's type, its message and the stack trace - the chain of method calls which led to it.
An exception that nobody catches
//Uncaught.java
public class Uncaught {
  static int parseAge(String text) {
    return Integer.parseInt(text);
  }

  static void register(String name, String ageText) {
    int age = parseAge(ageText);
    System.out.println(name + " registered, age " + age);
  }

  public static void main(String[] args) {
    register("Tom", "32");
    register("Harry", "thirty");
    System.out.println("This line is never reached");
  }
}
 OUTPUT of the above code
Tom registered, age 32
Exception in thread "main" java.lang.NumberFormatException: For input string: "thirty"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:564)
	at java.base/java.lang.Integer.parseInt(Integer.java:661)
	at Uncaught.parseAge(Uncaught.java:4)
	at Uncaught.register(Uncaught.java:8)
	at Uncaught.main(Uncaught.java:14)
Explanation of the output
  • The first call worked. In the second, Integer.parseInt("thirty") threw a NumberFormatException.
  • Nobody caught it, so it passed up through parseAge(), register() and main(), and the program ended - the last line never ran.
  • Read a stack trace from the top: the first line is the exception and its message, and the first line mentioning your code (Uncaught.parseAge) is usually where to start looking.
All exceptions are subclasses of java.lang.Throwable. Its two direct subclasses are Error, for serious problems in the JVM itself (such as OutOfMemoryError), which programs should not try to handle, and Exception, for problems a program can reasonably handle.
Checked exceptions - catch or declare A checked exception is any subclass of Exception which is not a subclass of RuntimeException. Checked exceptions describe problems that are outside the program's control but are expected to happen sometimes - a file which does not exist, a network connection which drops, a database which is unavailable. Common examples are IOException, FileNotFoundException, SQLException and InterruptedException.

For checked exceptions, the compiler enforces the catch or declare rule: any code which can throw one must either catch it with a try/catch block, or declare it with throws in the method's signature, passing the responsibility to the caller. Code which does neither does not compile -
A checked exception that is neither caught nor declared
//ReadConfig.java
import java.nio.file.Files;
import java.nio.file.Path;

public class ReadConfig {
  public static void main(String[] args) {
    String text = Files.readString(Path.of("config.txt"));
    System.out.println(text);
  }
}
 OUTPUT on compiling the above code
ReadConfig.java:7: error: unreported exception IOException; must be caught or declared to be thrown
    String text = Files.readString(Path.of("config.txt"));
                                  ^
1 error
Files.readString() declares throws IOException, so the compiler insists that we deal with it. Here are both ways -
Handling a checked exception: catch it, or declare it
//ReadConfigFixed.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;

public class ReadConfigFixed {
  // Option 1: catch the exception and handle it here
  static String readOrDefault(String file) {
    try {
      return Files.readString(Path.of(file));
    } catch (NoSuchFileException e) {
      System.out.println("  " + file + " not found - using defaults");
      return "mode=default";
    } catch (IOException e) {
      System.out.println("  could not read " + file + ": " + e.getMessage());
      return "mode=default";
    }
  }

  // Option 2: declare the exception and let the caller decide
  static String readRequired(String file) throws IOException {
    return Files.readString(Path.of(file));
  }

  public static void main(String[] args) {
    System.out.println("Config: " + readOrDefault("config.txt"));
    try {
      System.out.println("Config: " + readRequired("config.txt"));
    } catch (IOException e) {
      System.out.println("Required config missing: " + e);
    }
  }
}
 OUTPUT of the above code
  config.txt not found - using defaults
Config: mode=default
Required config missing: java.nio.file.NoSuchFileException: config.txt
Explanation of the code
Unchecked exceptions An unchecked exception is any subclass of RuntimeException (and, strictly, any Error). The compiler does not check them: methods do not have to declare them, and callers do not have to catch them. They usually signal a programming bug - something the code should have prevented - rather than an outside problem. The most common ones are -
  • NullPointerException - calling a method or reading a field on a null reference.
  • ArrayIndexOutOfBoundsException and StringIndexOutOfBoundsException - an index outside the valid range.
  • ArithmeticException - such as integer division by zero.
  • NumberFormatException - text which is not a valid number, passed to parseInt() and similar methods.
  • IllegalArgumentException and IllegalStateException - a method was given a bad argument, or called at the wrong time.
  • ClassCastException - an invalid cast between object types.
Common unchecked exceptions
//UncheckedDemo.java
import java.util.List;

public class UncheckedDemo {
  static String name = null;
  static int[] scores = {72, 85, 90};
  static Object value = "text";

  public static void main(String[] args) {
    String[] labels = {"name.length()", "scores[3]", "10 / 0", "parseInt(\"12a\")", "(Integer) value", "List.of(...).add()"};
    for (int i = 0; i < labels.length; i++) {
      try {
        switch (i) {
          case 0 -> System.out.println(name.length());
          case 1 -> System.out.println(scores[3]);
          case 2 -> System.out.println(10 / 0);
          case 3 -> System.out.println(Integer.parseInt("12a"));
          case 4 -> System.out.println((Integer) value);
          case 5 -> List.of("Tom").add("Harry");
        }
      } catch (RuntimeException e) {
        System.out.printf("%-20s -> %s: %s%n", labels[i], e.getClass().getSimpleName(), e.getMessage());
      }
    }
  }
}
 OUTPUT of the above code
name.length()        -> NullPointerException: Cannot invoke "String.length()" because "UncheckedDemo.name" is null
scores[3]            -> ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
10 / 0               -> ArithmeticException: / by zero
parseInt("12a")      -> NumberFormatException: For input string: "12a"
(Integer) value      -> ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
List.of(...).add()   -> UnsupportedOperationException: null
None of these statements needed a try block to compile - the program catches them only so that it can show them all. Notice the helpful NullPointerException messageClick to read tutorial on Java 14 helpful NullPointerExceptions - since Java 14, it names exactly which variable was null. The UnsupportedOperationException has no message (null); it was thrown because lists created by List.of() cannot be changed.

The right fix for an unchecked exception is usually to fix the code, not to catch the exception - check for null, check the index against the length, validate input before parsing it. Checked vs unchecked - comparison
CheckedUnchecked
SuperclassException (but not RuntimeException)RuntimeException (and Error)
Checked by the compiler?Yes - must be caught or declaredNo
Usually meansAn expected problem outside the program's controlA programming bug, or invalid input that should have been checked
Typical responseRecover: retry, use a default, report to the userFix the code; let it fail fast
ExamplesIOException, SQLException, InterruptedExceptionNullPointerException, IllegalArgumentException, ArithmeticException

Note - "Checked" and "unchecked" are decided purely at compile time. At runtime, both kinds are thrown, propagate and are caught in exactly the same way. Which kind should your own exceptions be? When you create your own exception classesClick to read tutorial: Creating Custom Exceptions in Java with Examples, a common guideline - from Effective Java and the Java documentation - is -
  • Use a checked exception when the caller can reasonably be expected to recover - for example, an InsufficientFundsException which the caller can report to the user.
  • Use an unchecked exception for programming errors and broken preconditions - such as a negative quantity passed to a method that requires a positive one.
  • When in doubt, many modern libraries and frameworks prefer unchecked exceptions, because checked exceptions force every layer of code in between to declare them, and do not work well with lambdas and streams.
Summary In this tutorial we first saw that an exception describes an unusual event, travels up the call stack until it is caught, and ends the program with a stack trace if it is not. We then looked at checked exceptions - subclasses of Exception outside RuntimeException - and the compiler's catch or declare rule, handling an IOException both ways. Next we met the common unchecked exceptions, which signal programming bugs and need neither catching nor declaring. We then compared the two kinds, and lastly saw when to choose each for your own exceptions. In the next tutorial, we will handle exceptions with try, catch, finally and multi-catchClick to read tutorial: Java try, catch, finally and Multi-catch with Examples.