Skip to main content

8.1 Exception Handling Overview

note

This guide is based on Java 21. For the latest specifications, refer to the official Java Documentation.

While developing programs, you will inevitably encounter various errors. Users may enter letters where numbers are required, a file to be read may have been deleted, or the network connection may drop. Java's exception handling is the essential technique of writing code in advance to handle these unexpected runtime situations — preventing abnormal program termination and maintaining normal execution.

1. Error vs. Exception

Java classifies problems that can occur during program execution into two broad categories.

Error

Problems like running out of memory (OutOfMemoryError) or stack overflow (StackOverflowError) that paralyze the system at a severe, unrecoverable level. Once they occur there is no way to prevent abnormal termination, so they are outside the realm a developer can handle at the code level.

// Example of StackOverflowError — infinite recursion
public class ErrorExample {
public static void infiniteRecursion() {
infiniteRecursion(); // calls itself endlessly → StackOverflowError!
}

public static void main(String[] args) {
infiniteRecursion();
}
}
// Output: Exception in thread "main" java.lang.StackOverflowError

Exception

Lighter-form errors caused by user mistakes or developer coding errors. They can be caught and recovered at the code level using try-catch. What we must study and defend against with code are these exceptions.

2. Exception Class Hierarchy

In Java, even exceptions and errors are treated as objects that ultimately inherit from java.lang.Object.

Object
└── Throwable
├── Error ← System-level severe errors (not handleable)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception ← Program-level errors (handleable)
├── IOException ← Checked Exception
├── SQLException ← Checked Exception
├── ClassNotFoundException ← Checked Exception
└── RuntimeException ← Unchecked Exception
├── NullPointerException
├── ArrayIndexOutOfBoundsException
├── ClassCastException
├── NumberFormatException
└── ArithmeticException

3. Checked Exception vs Unchecked Exception

Exception classes fall into two broad categories.

CategoryChecked ExceptionUnchecked Exception
InheritanceDirect descendant of ExceptionDescendant of RuntimeException
Compiler enforcementHandling mandatory (compile error if missing)Handling optional
Primary causeExternal environment (missing file, network error)Programmer mistake (null access, invalid cast)
Representative examplesIOException, SQLExceptionNullPointerException, ClassCastException
import java.io.FileReader;
import java.io.IOException;

public class CheckedExample {
public static void main(String[] args) {
// Checked Exception: the compiler forces you to handle it
// Without try-catch this causes a compile error!
try {
FileReader reader = new FileReader("nonexistent.txt");
} catch (IOException e) {
System.out.println("File not found: " + e.getMessage());
}
}
}

4. Common Exceptions You Will Encounter

Knowing these in advance dramatically reduces debugging time.

NullPointerException (NPE)

Occurs when you access a member of a null reference variable. The most common exception Java developers face.

public class NullPointerExam {
public static void main(String[] args) {
String name = null;

// Calling a method on a null variable throws NullPointerException!
try {
int len = name.length(); // error occurs!
} catch (NullPointerException e) {
System.out.println("name is null. Initialization required.");
// Java 14+: e.getMessage() provides a more detailed explanation
}

// Defensive coding: check for null before calling
if (name != null) {
System.out.println("Length: " + name.length());
}
}
}

ArrayIndexOutOfBoundsException

Occurs when accessing an array index outside its valid range.

public class ArrayExam {
public static void main(String[] args) {
int[] arr = {10, 20, 30}; // indices 0, 1, 2

try {
System.out.println(arr[5]); // index 5 doesn't exist!
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index out of bounds: " + e.getMessage());
}

// Correct array traversal
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}

NumberFormatException

Occurs when trying to convert a string to a number but the format is invalid.

public class NumberFormatExam {
public static void main(String[] args) {
String[] inputs = {"123", "abc", "45.6", ""};

for (String input : inputs) {
try {
int number = Integer.parseInt(input);
System.out.println("Conversion successful: " + number);
} catch (NumberFormatException e) {
System.out.println("Conversion failed [" + input + "]: not a valid number");
}
}
}
}

Output:

Conversion successful: 123
Conversion failed [abc]: not a valid number
Conversion failed [45.6]: not a valid number
Conversion failed []: not a valid number

ClassCastException

Occurs when forcing a type cast to an incompatible type.

public class ClassCastExam {
public static void main(String[] args) {
Object obj = "Hello"; // String object stored as Object

try {
// Trying to cast String to Integer → impossible!
Integer num = (Integer) obj;
} catch (ClassCastException e) {
System.out.println("Cast failed: " + e.getMessage());
}

// Safe approach: check with instanceof before casting
if (obj instanceof String str) { // Java 16+ pattern matching
System.out.println("String length: " + str.length());
}
}
}

5. Why Exception Handling Matters

Three Key Reasons for Exception Handling
  1. Program Stability: Build bulletproof code that doesn't die from hidden bugs waiting to explode.
  2. User Experience: Instead of a frightening stack trace, display friendly messages like "Service is temporarily unavailable."
  3. Debugging Support: Safely log error details so developers can fix bugs later.
// Unsafe code (no exception handling)
public class UnsafeCode {
public static void main(String[] args) {
String input = "abc";
int number = Integer.parseInt(input); // exception thrown → program dies!
System.out.println("Result: " + number); // never reached
}
}

// Safe code (with exception handling)
public class SafeCode {
public static void main(String[] args) {
String input = "abc";
try {
int number = Integer.parseInt(input);
System.out.println("Result: " + number);
} catch (NumberFormatException e) {
System.out.println("Please enter a valid number.");
// program continues running!
}
System.out.println("Program continues normally.");
}
}

6. Three Ways to Handle Exceptions

MethodSyntaxDescription
Handle directly with try-catchtry { } catch { }Handle the exception where it occurs
Delegate with throwsvoid method() throws IOExceptionPass handling responsibility to the caller
Throw intentionallythrow new Exception()Deliberately create and throw an exception object
import java.io.IOException;

public class ExceptionMethodsExample {

// Method 2: delegate exception handling to the caller with throws
public static void readFile(String path) throws IOException {
// This method does not handle the exception; it passes it up to the caller
throw new IOException("Cannot read file: " + path);
}

// Method 3: intentionally throw an exception
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return a / b;
}

public static void main(String[] args) {
// Method 1: handle directly with try-catch
try {
readFile("nonexistent.txt"); // calling a throws IOException method
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
}

try {
int result = divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("Calculation error: " + e.getMessage());
}

System.out.println("Program ends normally.");
}
}

Output:

File error: Cannot read file: nonexistent.txt
Calculation error: Cannot divide by zero
Program ends normally.

7. Exception Information Methods

public class ExceptionInfoExample {
public static void main(String[] args) {
try {
int[] arr = new int[3];
arr[10] = 100; // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
// 1. getMessage(): short exception message
System.out.println("Message: " + e.getMessage());

// 2. toString(): exception class name + message
System.out.println("toString: " + e.toString());

// 3. printStackTrace(): full call stack (essential for debugging)
System.out.println("--- Stack Trace ---");
e.printStackTrace();

// 4. getClass().getName(): exception class full name
System.out.println("Exception class: " + e.getClass().getName());
}
}
}
Caution

printStackTrace() is very useful during development/debugging, but in a production environment it can expose sensitive system information. Use a logging framework (Logback, Log4j, etc.) to write to log files instead.

8. Practical Example: Safe User Input Processing

import java.util.Scanner;

public class SafeInputExample {

// Utility method: safely convert string to integer
public static int parseIntSafe(String input, int defaultValue) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
System.out.println("Warning: '" + input + "' is not a valid number. Using default: " + defaultValue);
return defaultValue;
} catch (NullPointerException e) {
System.out.println("Warning: null input. Using default: " + defaultValue);
return defaultValue;
}
}

public static void main(String[] args) {
String[] testInputs = {"42", " 100 ", "abc", null, "3.14", "999"};

int total = 0;
for (String input : testInputs) {
int value = parseIntSafe(input, 0);
total += value;
System.out.println("Input: " + input + " → Converted: " + value);
}

System.out.println("Total: " + total);
}
}

Output:

Input: 42 → Converted: 42
Input: 100 → Converted: 100
Warning: 'abc' is not a valid number. Using default: 0
Input: abc → Converted: 0
Warning: null input. Using default: 0
Input: null → Converted: 0
Warning: '3.14' is not a valid number. Using default: 0
Input: 3.14 → Converted: 0
Input: 999 → Converted: 999
Total: 1141
Pro Tip

In practice, catching exceptions with a blanket catch (Exception e) is an anti-pattern. Specify concrete exception types so each situation gets appropriate handling. Also, empty catch blocks that do nothing are the worst habit — they silently hide bugs.

In the next chapter we will explore the most fundamental and essential weapon for controlling exceptions in Java: the try-catch block.