CodeGym /Courses /JAVA 25 SELF /Exception chaining (Exception Chaining)

Exception chaining (Exception Chaining)

JAVA 25 SELF
Level 24 , Lesson 2
Available

1. Introduction

In this lecture we’ll explore an important technique for working with exceptions — exception chaining. This technique helps preserve information about the root cause of an error, even when you “wrap” one exception in another.

In real-world applications, errors often occur deep inside the call stack — for example, when working with a database, file system, or network. Suppose you have a method that accesses a database and might throw a SQLException. But at the business-logic level, you don’t want to clutter the code with technical details and prefer to throw your own exception, for example, UserManagementException.

What happens if you just throw a new exception?

try {
    // something with the database
} catch (SQLException e) {
    throw new UserManagementException("Error working with users");
}

Problem:
In this case, the information about what exactly happened in the database (and the call stack!) is lost. In the logs, you’ll only see UserManagementException, with no clue about the cause.

2. Solution: wrapping the original exception (chaining)

Java lets you “wrap” one exception in another by passing the original exception as the cause to the constructor of the new exception. This is called exception chaining.

How to do it?

Most standard and custom exceptions provide a constructor that accepts a second parameter — Throwable cause:

public UserManagementException(String message, Throwable cause) {
    super(message, cause);
}

Usage:

try {
    // something with the database
} catch (SQLException e) {
    throw new UserManagementException("Error working with users", e);
}

Now, if you look at the stack trace (printStackTrace()), you’ll see both your exception and the entire chain down to the root cause!

3. How to get the cause of an exception

Any object of type Throwable has a getCause() method that returns the original exception (or null if there isn’t one).

Example:

try {
    // ...
} catch (UserManagementException e) {
    Throwable cause = e.getCause();
    if (cause != null) {
        System.out.println("Root cause: " + cause);
    }
    e.printStackTrace();
}

Why is this useful?

  • For debugging: you see not only “what went wrong” at the top level, but also where exactly the error occurred deeper in the stack.
  • For logging: you can write the entire chain of errors to the log.
  • For passing information between application layers: the business layer can “wrap” a technical exception in its own without losing details.

4. Example: exception chaining in a real application

Suppose you have a method that loads a user from the database:

public User loadUser(String username) throws UserManagementException {
    try {
        // Code that can throw SQLException
        // ...
    } catch (SQLException e) {
        throw new UserManagementException("Failed to load user: " + username, e);
    }
}

Where UserManagementException is your own exception:

public class UserManagementException extends Exception {
    public UserManagementException(String message, Throwable cause) {
        super(message, cause);
    }
}

What happens on error?

  • The logs will show both your exception and the original SQLException with all details.
  • If needed, you can access the root cause via getCause().

5. What a stack trace looks like with exception chaining

Sample output:

UserManagementException: Failed to load user: vasya
    at UserService.loadUser(UserService.java:15)
    ...
Caused by: java.sql.SQLException: Connection refused
    at ...

You can see everything here: the full call chain, where the business error occurred, and which technical exception caused it.

6. Hands-on: implement exception chaining

Step 1. Create your own exception:

public class UserManagementException extends Exception {
    public UserManagementException(String message) {
        super(message);
    }
    public UserManagementException(String message, Throwable cause) {
        super(message, cause);
    }
}

Step 2. Use the chain:

try {
    // something risky
} catch (SQLException e) {
    throw new UserManagementException("Error working with the DB", e);
}

Step 3. Handle at the top level:
At the very top level of the program, we catch our custom exception and print the error message along with the full chain of causes.

public class Main {
    public static void main(String[] args) {
        try {
            runUserManagement();
        } catch (UserManagementException e) {
            System.err.println("An error occurred: " + e.getMessage());
            // Print the chain of causes
            Throwable cause = e.getCause();
            while (cause != null) {
                System.err.println("Cause: " + cause.getMessage());
                cause = cause.getCause();
            }
        }
    }

    private static void runUserManagement() throws UserManagementException {
        try {
            // simulate DB error
            throw new SQLException("No connection to the DB");
        } catch (SQLException e) {
            throw new UserManagementException("Error working with the DB", e);
        }
    }
}

7. Common mistakes when working with exception chains

Error No. 1: throwing a new exception without a cause.

catch (SQLException e) {
    throw new UserManagementException("Error", /* no cause! */);
}

Bad: information about the root cause is lost.

Error No. 2: you didn’t implement a constructor with a cause in your exception.
If your exception class doesn’t have a constructor with Throwable cause, you won’t be able to pass the cause — you’ll have to add it manually.

Error No. 3: catching and swallowing the exception without propagating it.

catch (SQLException e) {
    // Just log and keep quiet
}

Bad: the error gets “lost,” and the program continues working incorrectly.

try {
    userService.loadUser("vasya");
} catch (UserManagementException e) {
    System.err.println("Error: " + e.getMessage());
    if (e.getCause() != null) {
        System.err.println("Root cause: " + e.getCause());
    }
    e.printStackTrace();
}
1
Task
JAVA 25 SELF, level 24, lesson 2
Locked
Data Detective: Finding the Root Cause of a Failure
Data Detective: Finding the Root Cause of a Failure
1
Task
JAVA 25 SELF, level 24, lesson 2
Locked
Path to the error: Chain of failures in the reporting system
Path to the error: Chain of failures in the reporting system
1
Task
JAVA 25 SELF, level 24, lesson 2
Locked
"External error" with a "cause": Investigating the root cause
"External error" with a "cause": Investigating the root cause
1
Task
JAVA 25 SELF, level 24, lesson 2
Locked
Space catastrophe: Multi-level chain of failures
Space catastrophe: Multi-level chain of failures
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION