Exceptions in Java: Hierarchy and Types
Errors, checked exceptions and unchecked exceptions sit in one hierarchy, and knowing which is which decides how you should react.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
What an exception is
An exception is an object describing an abnormal condition. When one is thrown, normal execution stops and the JVM searches up the call stack for a handler. If none is found, the thread ends and the stack trace is printed.
The hierarchy
Throwable
|
+-- Error serious problems, do not catch
| OutOfMemoryError
| StackOverflowError
|
+-- Exception application problems
|
+-- RuntimeException unchecked
| NullPointerException
| IllegalArgumentException
| IllegalStateException
| ArithmeticException
| IndexOutOfBoundsException
| ClassCastException
| NumberFormatException
|
+-- IOException checked
+-- SQLException checked
+-- (your own checked exceptions)Everything that can be thrown is a Throwable. The split that matters day to day is between Error, RuntimeException and everything else under Exception.
Errors
// Do not do this
try {
deepRecursion();
} catch (StackOverflowError e) {
// the stack is already unreliable; continuing is not meaningful
}An Error signals a condition the application cannot sensibly recover from. Catch one only at the very top of a program to log it before shutting down, and never to carry on as if nothing happened.
Checked exceptions
public String readFirstLine(Path path) throws IOException { // declared
try (BufferedReader reader = Files.newBufferedReader(path)) {
return reader.readLine();
}
}A checked exception must be either caught or declared with throws. The compiler enforces it. The intent is that these represent conditions a correct program should anticipate, such as a missing file or a dropped connection.
Unchecked exceptions
public double divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Divisor must not be zero");
}
return (double) a / b;
}Anything extending RuntimeException needs no declaration and no handler. These usually indicate a programming mistake: a null that should not be null, an argument that was never valid, an index outside the array.
Comparison
| Aspect | Checked | Unchecked |
|---|---|---|
| Superclass | Exception, not RuntimeException | RuntimeException |
| Compiler enforced | Yes | No |
| Must be declared | Yes | No |
| Typically means | An expected external failure | A bug in the code |
| Example | IOException | NullPointerException |
Checked exceptions are a design decision unique to Java, and a contested one. The consensus in modern Java is to use them sparingly: only when the caller can realistically do something different in response. Otherwise an unchecked exception keeps signatures honest.
The exceptions you will meet most
| Exception | Typical cause |
|---|---|
NullPointerException | Calling a method on a null reference |
ArrayIndexOutOfBoundsException | An index below zero or at or beyond the length |
NumberFormatException | Integer.parseInt on text that is not a number |
ClassCastException | Casting to a type the object is not |
IllegalArgumentException | An argument outside its allowed range |
IllegalStateException | A method called at the wrong time |
ConcurrentModificationException | Structurally changing a collection while iterating |
ArithmeticException | Integer division by zero |
Helpful NullPointerException messages
Cannot invoke "String.length()" because the return value of
"Note.title()" is nullSince Java 14 the runtime describes exactly which reference was null. This turned the least informative exception in Java into one of the most useful, and it is on by default from Java 15.
Reading a stack trace
Exception in thread "main" java.lang.NumberFormatException: For input string: "ten"
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at com.example.notes.Importer.parseRow(Importer.java:41) <-- your code
at com.example.notes.Importer.run(Importer.java:22)
at com.example.notes.Main.main(Main.java:9)
Caused by: ...Read the top line for the type and message, then scan down for the first frame in your own code. The Caused by section, when present, holds the original failure and is usually the more informative part.
Exceptions cost more than a return
Creating an exception captures the stack trace, which is not free. That is a reason to avoid using exceptions for ordinary control flow, not a reason to avoid them for genuine failures.
Common mistakes
- Catching
ExceptionorThrowablebroadly and hiding real problems. - Treating every failure as checked, which pushes
throwsclauses through the whole codebase. - Catching
NullPointerExceptioninstead of checking for null. - Using exceptions to signal an ordinary outcome, such as "not found".
- Catching
Errorand continuing.
Best practices
- Throw the most specific type that describes the problem.
- Use unchecked exceptions for programming errors and invalid arguments.
- Reserve checked exceptions for conditions a caller can genuinely handle differently.
- Include the offending value in the message, never the whole object if it holds secrets.
- Let an exception travel to a layer that can actually decide what to do.
Practice
- Classify each as checked, unchecked or error:
IOException,ArithmeticException,OutOfMemoryError,SQLException,ClassCastException. - Why does the compiler insist on handling
IOExceptionbut notNullPointerException? - Write a method that throws
IllegalArgumentExceptionfor a negative age, with a useful message. - Read a stack trace from your own code and identify the first line you wrote.
- Give one case where a checked exception is the right choice and one where it is not.
Conclusion
Errors are not yours to handle, unchecked exceptions usually mean a bug, and checked exceptions mean a failure the caller was expected to plan for. Choosing correctly is most of good exception design.