WL
Java Full Stack Developer
Wassim Lagnaoui

Lesson 08: Exception Handling

Master Java exception handling to write robust, error-resistant applications that gracefully handle unexpected situations.

Introduction

In real-world applications, things go wrong: files might not exist, network connections can fail, users enter invalid data, or calculations result in division by zero. Without proper exception handling, these problems would crash your program and frustrate users. Exception handling in Java provides a structured way to detect, handle, and recover from errors gracefully. Instead of your application suddenly stopping with a cryptic error message, you can catch problems, provide meaningful feedback, clean up resources, and keep your program running smoothly. This lesson teaches you how to use try-catch blocks, create custom exceptions, handle resources safely, and follow best practices that make your code resilient and professional.

Understanding Exceptions

Definition

An exception is an event that occurs during program execution that disrupts the normal flow of instructions. When an error condition arises, Java creates an exception object containing information about the error and "throws" it up the call stack. Exceptions can be checked (must be handled) or unchecked (runtime exceptions that indicate programming errors).

Analogy

Think of exceptions like fire alarms in a building. When something goes wrong (smoke detected), the alarm system alerts everyone, indicates where it happened (stack trace), and activates emergency procedures (exception handling). Some alarms require immediate action (checked exceptions), while others indicate user errors like burnt toast (unchecked exceptions).

Examples

Division by zero:

int result = 10 / 0; // throws ArithmeticException

Invalid array access:

int[] numbers = {1, 2, 3};
int value = numbers[5]; // throws ArrayIndexOutOfBoundsException

Checked vs Unchecked:

new FileReader("data.txt"); // checked: IOException
List list = List.of("a", "b");
list.get(5); // unchecked: IndexOutOfBoundsException

try-catch-finally

Definition

The try-catch-finally structure handles exceptions gracefully. The try block contains code that might throw; catch handles specific types; finally always executes for cleanup.

Analogy

Like setting up camp: try to light a fire, catch problems with backup plans, and finally put food away and secure gear no matter what.

Examples

Basic try-catch:

try {
  int result = 10 / 0;
} catch (ArithmeticException e) {
  System.out.println("Cannot divide by zero!");
}

Multiple catch (specific to general):

try {
  String content = Files.readString(Path.of(fileName));
} catch (NoSuchFileException e) {
  System.out.println("File not found: " + fileName);
} catch (IOException e) {
  System.out.println("Error reading file: " + e.getMessage());
}

finally for cleanup:

FileInputStream in = null;
try {
  in = new FileInputStream("data.txt");
  // process
} catch (IOException e) {
  System.out.println("File error: " + e.getMessage());
} finally {
  if (in != null) in.close();
}

Throwing Exceptions

Definition

Use throw to signal errors and throws to declare checked exceptions that callers must handle. This creates a clear error contract.

Analogy

Like a quality inspector stopping a production line and escalating with a report so supervisors decide what to do.

Examples

Validate and throw:

if (age < 0) throw new IllegalArgumentException("Age cannot be negative");

Method declares throws:

public void readFile(String name) throws IOException {
  Files.readString(Path.of(name));
}

Caller handles declared exception:

try {
  readFile("data.txt");
} catch (IOException e) {
  System.out.println("Failed to read file");
}

Rethrow with context:

try {
  processOrder(order);
} catch (Exception e) {
  throw new IllegalStateException("Failed to process order " + order.getId(), e);
}

Custom Exceptions

Definition

Create domain-specific exception types to make errors clearer. Extend RuntimeException (unchecked) or Exception (checked).

Analogy

Like departments using specific error codes (InvalidAddress, OutOfStock) so issues are obvious and routed correctly.

Examples

Simple custom exception:

class InvalidEmailException extends RuntimeException {
  public InvalidEmailException(String msg) { super(msg); }
}

Throw and catch:

if (!email.contains("@")) throw new InvalidEmailException("Email must contain @");
try { validateUser(user); } catch (InvalidEmailException e) { showEmailError(e.getMessage()); }

Custom with extra data:

class InsufficientFundsException extends RuntimeException {
  final double requested, available;
  public InsufficientFundsException(double r, double a){
    super("Insufficient funds: requested " + r + ", available " + a);
    this.requested = r; this.available = a;
  }
}

Try-with-Resources

Definition

try-with-resources automatically closes resources that implement AutoCloseable, preventing leaks without verbose finally blocks.

Analogy

Like smart lights that always turn off when you leave the room, even during emergencies.

Examples

Read file safely:

try (BufferedReader r = Files.newBufferedReader(path)) {
  return r.readLine();
}

Database query:

try (Connection c = DriverManager.getConnection(url);
     Statement s = c.createStatement()) {
  ResultSet rs = s.executeQuery("SELECT * FROM users");
  return processResults(rs);
}

Custom AutoCloseable:

class TimedOperation implements AutoCloseable {
  long start = System.currentTimeMillis();
  public void close(){
    System.out.println("Operation took: " + (System.currentTimeMillis()-start) + "ms");
  }
}

Exception Handling Best Practices

Definition

Write code that fails gracefully, communicates clearly, and cleans up safely. Catch specific types, avoid empty catches, use try-with-resources, fail fast, and don't use exceptions for normal control flow.

Analogy

Like hospital emergency protocols: specific responses for each situation, documentation, no ignored emergencies, and automatic maintenance.

Examples

Catch specific exceptions first:

try {
  processFile(fileName);
} catch (FileNotFoundException e) {
  createDefaultFile(fileName);
} catch (IOException e) {
  logError("File processing failed", e);
}

Never ignore exceptions:

try { saveImportantData(); } catch (IOException e) {
  logger.error("Failed to save data", e);
  throw new RuntimeException("Data save failed", e);
}

Don't use exceptions for control flow:

if (index >= 0 && index < array.length) return array[index];
else return defaultValue;

Summary

Exception handling is essential for building robust Java applications that gracefully handle errors and unexpected situations. You've learned to use try-catch-finally blocks to handle exceptions, throw your own exceptions with meaningful messages, create custom exception types for specific business errors, and use try-with-resources for automatic cleanup. Following best practices like catching specific exceptions, providing helpful error messages, and failing fast on programming errors will make your applications more reliable and easier to debug. In the next lesson, you'll apply these exception handling skills when working with File I/O operations, where proper error handling is crucial for dealing with missing files, permission issues, and other I/O problems.

Programming Challenge

Challenge: Robust File Processor

Task: Create a file processing system that reads student data from a CSV file and handles various error conditions gracefully.

Requirements:

  1. Create a custom exception StudentDataException that includes the line number and invalid data
  2. Create a Student class with: name, age, grade (A-F), and email
  3. Write a method processStudentFile(String filename) that:
    • Uses try-with-resources to read the CSV file safely
    • Validates each student record and throws StudentDataException for invalid data
    • Handles FileNotFoundException and IOException appropriately
    • Returns a list of valid students and logs errors for invalid records
  4. Create a method that demonstrates both handling and propagating exceptions
  5. Include proper error messages with context (line numbers, invalid values)

Test scenarios:

  • Missing file
  • Invalid age (negative or non-numeric)
  • Invalid grade (not A-F)
  • Invalid email format
  • Empty or malformed lines

Learning Goals: Practice custom exceptions, try-with-resources, exception propagation, and comprehensive error handling in a realistic file processing scenario.