Table of contents
1.
Introduction
2.
Exception Propagation
3.
Nested Try Block
4.
Rules for Exception Propagation in Java
5.
Java Exception Propagation Example
6.
Frequently Asked Questions
6.1.
What happens if an exception is not caught & handled?
6.2.
Can we catch & handle an exception in the same method where it occurs?
6.3.
What is the purpose of the throws keyword in a method signature?
7.
Conclusion
Last Updated: Nov 8, 2024
Easy

Exception Propagation in Java

Author Rahul Singh
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

In Java, exceptions are a way to handle errors and unexpected situations that can occur during program execution. When an exception is thrown, it disrupts the normal flow of the program. Exception propagation is a process that allows a method to throw an exception back to the caller. This means that when an exception occurs in a method, it doesn't need to handle the exception itself. Instead, it can pass the exception up the call stack, allowing higher-level methods to either handle it or pass it on until it reaches a method that wants to handle it. 

Exception Propagation in Java

In this article, we will discuss exception propagation in Java in detail, like syntax, rules, and examples.

Exception Propagation

Exception propagation is a mechanism in Java where an exception is passed from the method in which it occurred to the calling methods in the call stack until it is caught and handled. When an exception is thrown, the Java runtime system attempts to find an exception handler that can catch and handle the exception. If the current method does not have a suitable exception handler, the exception is propagated to the calling method. This process continues until either the exception is caught or it reaches the top of the call stack.


Exception propagation allows you to separate the code that detects an error from the code that handles it. With the help of propagating exceptions, you can centralize the exception-handling logic in a specific part of your program, which makes it easier to manage and maintain. This also helps keep the code clean and readable, as you don't need to clutter each method with exception-handling code.

For example :

Suppose we have three methods: `method1()`, `method2()`, and `method3()`. `method1()` calls `method2()`, which in turn calls `method3()`. If an exception occurs in `method3()` and it is not handled there, it will be propagated to `method2()`. If `method2()` also doesn't handle the exception, it will be further propagated to `method1()`.

The code for this exception propagation is:

public class ExceptionPropagationExample {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("Exception caught in main(): " + e.getMessage());
        }
    }

    public static void method1() throws Exception {
        method2();
    }

    public static void method2() throws Exception {
        method3();
    }

    public static void method3() throws Exception {
        throw new Exception("Exception occurred in method3()");
    }
}
You can also try this code with Online Java Compiler
Run Code


In this example, `method3()` throws an exception with the message "Exception occurred in method3()." Since `method3()` doesn't handle the exception, it is propagated to `method2()`. `method2()` also doesn't handle the exception and propagates it further to `method1()`. Finally, `method1()` propagates the exception to the `main()` method, where it is caught and handled.

The output of this program will be:

Exception caught in main(): Exception occurred in method3()


This example shows how an exception propagates through the call stack until it is caught and handled in the `main()` method.

Nested Try Block

In Java, you can use nested try blocks to handle exceptions at different levels of granularity. A nested try block is a try block that is defined inside another try block. This allows you to handle exceptions separately for different parts of your code.

The syntax for nested try blocks is:

try {
    // outer try block
    try {
        // inner try block
        // code that may throw an exception
    } catch (ExceptionType1 e1) {
        // exception handler for inner try block
    }
} catch (ExceptionType2 e2) {
    // exception handler for outer try block
}


In this syntax, the outer try block encloses the inner try block. If an exception occurs within the inner try block, it will be caught by the corresponding catch block of the inner try block. If the inner try block does not have a suitable catch block for the exception, the exception will be propagated to the outer try block. The outer try block can then handle the exception with its own catch block.

For example : 

public class NestedTryBlockExample {
    public static void main(String[] args) {
        try {
            // outer try block
            int[] numbers = {1, 2, 3};
            try {
                // inner try block
                System.out.println(numbers[5]); // accessing an invalid index
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage());
            }
            int result = 10 / 0; // division by zero
        } catch (ArithmeticException e) {
            System.out.println("Caught ArithmeticException: " + e.getMessage());
        }
    }
}
You can also try this code with Online Java Compiler
Run Code


In this example, we have an outer try block that contains an inner try block. The inner try block attempts to access an invalid index of an array, which throws an `ArrayIndexOutOfBoundsException`. This exception is caught by the catch block of the inner try block. After the inner try block, we have a division by zero operation, which throws an `ArithmeticException`. This exception is caught by the catch block of the outer try block.

The output of this program will be:

Caught ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
Caught ArithmeticException: / by zero
Syntax - Nested Method Calls:


Exception propagation is often used in combination with nested method calls. When a method calls another method and an exception occurs in the called method, the exception is propagated back to the calling method. This process continues until the exception is caught and handled or until it reaches the top of the call stack.

For example : 

public class NestedMethodCallsExample {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("Exception caught in main(): " + e.getMessage());
        }
    }

    public static void method1() throws Exception {
        method2();
    }

    public static void method2() throws Exception {
        method3();
    }


    public static void method3() throws Exception {
        throw new Exception("Exception occurred in method3()");
    }
}

 

In this example, we have three methods: `method1()`, `method2()`, & `method3()`. `method1()` calls `method2()`, which in turn calls `method3()`. Inside `method3()`, an exception is thrown with the message "Exception occurred in method3()".

The exception is then propagated from `method3()` to `method2()`. Since `method2()` doesn't handle the exception, it is further propagated to `method1()`. `method1()` also doesn't handle the exception, so it is finally propagated to the `main()` method, where it is caught & handled.

The output of this program will be:

Exception caught in main(): Exception occurred in method3()


When using nested method calls, it's important to consider which method should handle the exception. You can choose to handle the exception in the method where it occurs, or you can let it propagate to the calling method and handle it there. The decision depends on your program's specific requirements and the context in which the exception occurs.

Note: With the help of nested method calls and exception propagation, you can separate the exception-handling logic from the main flow of your program, which makes it more modular and easier to maintain.

Rules for Exception Propagation in Java

1. If an exception is not caught & handled within a method, it is propagated to the calling method. This process continues until the exception is caught or reaches the top of the call stack.
 

2. When an exception is propagated, the program flow is interrupted, & the control is transferred to the nearest exception handler in the call stack that can handle the exception.
 

3. If a method declares that it throws an exception using the `throws` keyword, it must either handle the exception or specify the exception in its method signature. This allows the calling method to be aware of the possible exceptions that may be thrown.
 

4. If a method does not declare that it throws an exception, but an exception occurs within the method, the exception is propagated to the calling method, even if the calling method does not declare that it throws the exception.
 

5. When an exception is propagated, the stack trace (the sequence of method calls that led to the exception) is maintained. This information is useful for debugging & identifying the source of the exception.
 

6. If an exception is not caught and handled anywhere in the call stack, the Java runtime system will terminate the program and print the stack trace to the console.
 

7. It is considered good practice to handle exceptions at the appropriate level of abstraction. Low-level exceptions should be handled in the methods where they occur, while high-level exceptions can be propagated to the calling methods for centralized handling.
 

8. Using exception propagation, you can separate the error-handling code from the program's normal flow, making the code more readable and maintainable.

Java Exception Propagation Example

In this example, we will create a program that simulates a bank transaction & see how exceptions are propagated through different methods.

public class BankTransaction {
    public static void main(String[] args) {
        try {
            double balance = 1000.0;
            double amount = 1500.0;
            balance = withdraw(balance, amount);
            System.out.println("Withdrawal successful. New balance: " + balance);
        } catch (InsufficientFundsException e) {
            System.out.println("Exception caught in main(): " + e.getMessage());
        }
    }

    public static double withdraw(double balance, double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Insufficient funds. Cannot withdraw " + amount);
        }
        return balance - amount;
    }
}


class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}


In this example, we have a custom exception class called `InsufficientFundsException` that extends the `Exception` class. This exception is thrown when a withdrawal amount exceeds the available balance.

The `withdraw()` method takes the current balance and the withdrawal amount as parameters. If the withdrawal amount is greater than the balance, it throws an `InsufficientFundsException` with an appropriate error message. Otherwise, it deducts the amount from the balance and returns the updated balance.

In the `main()` method, we initialize a balance of 1000.0 and attempt to withdraw an amount of 1500.0. We call the `withdraw()` method within a try block. If an `InsufficientFundsException` is thrown, it will be caught by the catch block in the `main()` method.

When we run this program, the output will be:

Exception caught in main(): Insufficient funds. Cannot withdraw 1500.0


In this example, the exception thrown in the `withdraw()` method is propagated to the `main()` method, where it is caught & handled. The error message from the exception is displayed, indicating that there are insufficient funds for the withdrawal.

Note: This example shows how exceptions can be propagated from one method to another and how they can be caught and handled at the appropriate level.

Frequently Asked Questions

What happens if an exception is not caught & handled?

If an exception is not caught and handled, it will propagate up the call stack until it reaches the top-level method (e.g., main()). If it is not caught there either, the Java runtime system will terminate the program and print the stack trace to the console.

Can we catch & handle an exception in the same method where it occurs?

Yes, you can catch and handle an exception in the same method where it occurs using a try-catch block. This is useful when you want to handle the exception locally and prevent it from propagating to the calling methods.

What is the purpose of the throws keyword in a method signature?

The throws keyword in a method signature is used to declare the exceptions that a method may throw. It informs the calling methods about the possible exceptions, allowing them to handle or propagate the exceptions accordingly. If a method throws a checked exception, it must be declared using the throws keyword.

Conclusion

In this article, we discussed exception propagation in Java. We learned how exceptions are passed from the method where they occur to the calling methods in the call stack until they are caught and handled. We also looked into the syntax of nested try blocks and nested method calls and the rules that take care of exception propagation. 

You can also check out our other blogs on Code360.

Live masterclass