Clean β’ Professional
Exception Chaining is a mechanism in Java that allows you to link one exception to another, so you can throw a new exception while preserving the original cause.
This is extremely useful when a low-level exception should not be exposed directly, but its root cause must still be traceable.
Throwing a higher-level exception while keeping the original (root) exception attached as the cause.
This ensures:
Java supports this using:
Throwable(Throwable cause) constructorinitCause(Throwable cause) methodgetCause() method
Throwable(Throwable cause) Constructorthrow new Exception("Top level exception", new NullPointerException("Root cause")
initCause(Throwable cause) MethodException ex = new Exception("Top level exception");
ex.initCause(new ArithmeticException("Root cause"));
throw ex;
getCause() Methodtry {
// code that throws exception
} catch (Exception e) {
System.out.println("Cause: " + e.getCause());
}
public class Test {
public static void main(String[] args) {
try {
divide();
} catch (Exception e) {
e.printStackTrace();
}
}
static void divide() throws Exception {
try {
int num = 10 / 0; // Low-level exception
} catch (ArithmeticException e) {
throw new Exception("Error occurred while dividing", e); // Chaining
}
}
}
Output
java.lang.Exception: Error occurred while dividing
Caused by: java.lang.ArithmeticException: / by zero
class InvalidDataException extends Exception {
public InvalidDataException(String message, Throwable cause) {
super(message, cause);
}
}
public class Main {
public static void process() throws InvalidDataException {
try {
String s = null;
s.length(); // NullPointerException
} catch (NullPointerException e) {
throw new InvalidDataException("Data processing failed", e);
}
}
public static void main(String[] args) {
try {
process();
} catch (InvalidDataException e) {
e.printStackTrace();
}
}
}
Exception ex = new Exception("Top level error");
ex.initCause(new NullPointerException("Root cause error"));
throw ex;
We will see how a request flows through:

Controller Layer β Service Layer β DAO Layer β Database
The controller receives the userβs request (e.g., register a new user).
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public String registerUser(@RequestBody User user) {
userService.registerUser(user); // call service layer
return "User registered successfully!";
}
}
The service layer performs business rules such as validations.
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
public void registerUser(User user) {
// Business rules
if (user.getEmail() == null || user.getEmail().isEmpty()) {
throw new IllegalArgumentException("Email cannot be empty");
}
// Password rule
if (user.getPassword().length() < 6) {
throw new IllegalArgumentException("Password must be at least 6 characters");
}
// Call DAO
userDAO.save(user);
}
}
The DAO layer actually interacts with the database.
@Repository
public class UserDAOImpl implements UserDAO {
@Override
public void save(User user) {
// Example: JDBC code
String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)";
// Database logic hereβ¦
System.out.println("User saved to database: " + user.getName());
}
@Override
public User findById(int id) {
// fetch from DB (logic not shown)
return new User("Demo User", "[email protected]", "123456");
}
}
Actual tables stored in MySQL / Oracle / PostgreSQL.
users table
| id | name | password | |
|---|---|---|---|
| 1 | John Doe | [email protected] | ***** |
User β sends registration request
β
Controller Layer β receives & forwards request
β
Service Layer β validates data & applies business rules
β
DAO Layer β saves data to database
β
Database β stores the information
β
Result β Success response is returned to user
java.lang.Exception: Top level error
at ...
Caused by: java.lang.NullPointerException: Root cause error
at ...
This βCaused byβ line is the chained exception.
| Benefit | Explanation |
|---|---|
| Preserves Root Cause | Never lose the original error |
| Better Debugging | Full trace from top layer to root |
| Clear Context | Each layer adds meaningful messages |
| Best Practice | Essential for enterprise applications |
| Cleaner Code | Reduces confusion and error hiding |
| Method | Purpose |
|---|---|
getCause() | Returns original cause |
initCause(Throwable cause) | Sets cause manually |
| Constructor with cause | new Exception("msg", cause) |
Use it in: