🌟 Chapter 35: Exception Handling (Part 2) - Master Exception Handling in Java! 🌟

Rohit GawandeRohit Gawande
26 min read

Table of contents

1. Introduction

1.1 Importance of Exception Handling in Software Development

Exception handling is a fundamental aspect of building reliable and robust software. Its importance lies in:

  • Maintaining Application Stability: Without exception handling, a program would abruptly terminate whenever an error occurs, leading to a poor user experience.

  • Debugging and Error Logging: Exception handling provides mechanisms like stack traces, which help identify and debug issues effectively.

  • Resource Management: Proper exception handling ensures critical resources like files, database connections, and memory are cleaned up even in the face of errors.

  • Encapsulation of Error Logic: It keeps the core business logic clean by segregating error-handling code.

  • Enhanced User Experience: Graceful error messages can guide users rather than presenting them with cryptic system-generated errors.

1.2 Key Terms

  • Exception: An event during program execution that disrupts the normal flow of instructions, such as ArithmeticException, IOException, etc.

  • Try-Catch Block: A construct to handle exceptions. Code that may throw an exception is written inside a try block, while the catch block provides the handling logic.

      try {
          int result = 10 / 0; // This will throw ArithmeticException
      } catch (ArithmeticException e) {
          System.out.println("Division by zero is not allowed.");
      }
    
  • Throws Keyword: Used in method declarations to indicate the method might throw specific exceptions, which need to be handled by the calling code.

      void readFile() throws IOException {
          // File reading logic
      }
    
  • Finally Block: A block of code that always executes after the try and catch blocks, regardless of whether an exception occurred. It’s often used for resource cleanup.

1.3 Overview of Java's Exception Hierarchy

Java's exception hierarchy is rooted in the Throwable class and divides into two main branches:

  1. Checked Exceptions:

    • Must be declared in the throws clause or handled using try-catch.

    • Represents recoverable conditions (e.g., IOException, SQLException).

  2. Unchecked Exceptions:

    • Do not need explicit handling.

    • Represent programming errors (e.g., NullPointerException, ArrayIndexOutOfBoundsException).

Hierarchy Diagram:

Throwable
├── Error (e.g., OutOfMemoryError)
└── Exception
    ├── Checked Exceptions (e.g., IOException)
    └── RuntimeException (Unchecked Exceptions, e.g., NullPointerException)

2. Previous Class Recap: Basics of Exception Handling

2.1 What is an Exception, and Why is it Crucial to Handle?

An exception is an unexpected event that occurs during program execution, disrupting its normal flow. Examples include dividing by zero, accessing an invalid array index, or attempting to open a non-existent file.

Handling exceptions is crucial because:

  • It prevents abrupt program termination.

  • It allows the program to recover and continue running.

  • It ensures that resources like files and database connections are properly closed.

2.2 Basic Try-Catch Block Syntax and Flow

The try-catch block is the cornerstone of exception handling in Java. Its syntax is as follows:

try {
    // Code that might throw an exception
} catch (ExceptionType e) {
    // Code to handle the exception
}

Flow:

  1. The try block is executed.

  2. If an exception occurs:

    • The control shifts to the corresponding catch block.

    • Exception details are captured in the catch parameter (e in this case).

  3. If no exception occurs, the catch block is skipped.

Example:

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[5]); // Throws ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Invalid index accessed!");
}

2.3 Brief Overview of Keywords Associated with Exception Handling

  1. try: Defines the block of code that might throw an exception.

  2. catch: Catches and handles exceptions thrown in the try block.

  3. finally: A block that always executes, commonly used for cleanup tasks.

  4. throws: Used in method signatures to indicate the exceptions a method might throw.

  5. throw: Used to explicitly throw an exception from code.

     throw new IllegalArgumentException("Invalid argument");
    

These concepts lay the groundwork for mastering exception handling in Java, ensuring robust and maintainable code.


3. Case Studies on Exception Handling

3.1 Case 1: No Exception Raised

3.1.1 Statements Executed When No Exception Occurs When no exception occurs inside the try block, all the statements within the block will be executed sequentially, and the catch block is skipped. The program continues executing normally after the try-catch structure.

3.1.2 Code Example

statement-1;
try {
    statement-2; // Executed
    statement-3; // Executed
    statement-4; // Executed
} catch (Exception e) {
    statement-5; // Skipped (no exception)
}
statement-6; // Executed

3.1.3 Execution Flow Explained

  • statement-1: Executed first, outside of the try-catch block.

  • statement-2: Executed as no exception occurs yet.

  • statement-3: Executed next.

  • statement-4: Finally, this statement is executed within the try block.

  • catch block: The catch block is skipped since no exception was raised.

  • statement-6: Executed after the try-catch block.

In this case, everything executes in sequence, and the program finishes normally without interruption.


3.2 Case 2: Exception Raised and Catch Block Matched

3.2.1 Execution When an Exception is Raised at Statement-3 If an exception occurs in the try block (e.g., at statement-3), and there is a corresponding catch block to handle that exception, the following happens:

  1. Statements before the exception are executed.

  2. The program control moves to the catch block when an exception is thrown.

  3. The catch block handles the exception, allowing the program to continue execution normally after the block.

3.2.2 Code Example and Explanation

statement-1;
try {
    statement-2; // Executed
    statement-3; // Exception raised here (e.g., ArithmeticException)
    statement-4; // Skipped (because exception occurred)
} catch (ArithmeticException e) {
    statement-5; // Executed (exception caught here)
}
statement-6; // Executed

3.2.3 Output Analysis

  • statement-1: Executed first.

  • statement-2: Executed without error.

  • statement-3: Exception occurs (e.g., division by zero).

  • catch block: Catches the exception and executes statement-5.

  • statement-6: Executed after the catch block, leading to normal termination.

The program does not terminate abruptly because the exception was caught and handled, allowing for a smooth continuation after the exception.


3.3 Case 3: Exception Raised and No Matching Catch Block

3.3.1 Consequences of Unmatched Exception Handling When an exception occurs, but there is no corresponding catch block to handle the specific exception type, the program will terminate abnormally. The exception is propagated up the call stack until it is either caught by a matching catch block or the default exception handler.

3.3.2 Code Snippet and Analysis

statement-1;
try {
    statement-2; // Executed
    statement-3; // Exception occurs here
    statement-4; // Skipped
} catch (NullPointerException e) {
    statement-5; // This block doesn't catch the ArithmeticException
}
statement-6; // Not reached due to abnormal termination

3.3.3 Output Analysis

  • statement-1: Executed first.

  • statement-2: Executed without error.

  • statement-3: Throws an ArithmeticException (but catch block only handles NullPointerException).

  • The exception is not caught by the catch block, leading to abnormal termination of the program.

  • statement-6: Not reached due to the exception propagation and program termination.

In this case, the program terminates unexpectedly because there is no matching catch block for the type of exception thrown.


3.4 Case 4: Exception Raised Outside the Try Block

3.4.1 Explaining Risky Code Outside the Try Block If risky code that can throw exceptions is not placed inside the try block, the exception will not be caught by the try-catch structure. Instead, it will be handled by the default exception handler, and the program may terminate.

3.4.2 Default Handler’s Role and Program Termination If the exception occurs outside the try block (e.g., in statement-1, statement-5, or statement-6), there is no try-catch structure to catch it, and the default Java handler will deal with it. This often results in an abnormal termination of the program.

3.4.3 Visualizing the Stack Using Diagrams Consider the following example where an exception is raised outside the try block:

statement-1;  // Exception occurs here
try {
    statement-2;
    statement-3;
} catch (Exception e) {
    statement-4;
}
statement-5; // Exception will prevent reaching this statement
  1. statement-1: If an exception occurs here, no try-catch block is present, and it causes an immediate program halt.

  2. The stack trace is printed, showing where the exception occurred.

  3. The program does not proceed to statement-5, and the default exception handler will terminate the program.

Conclusion This case demonstrates the importance of placing all risky code inside the try block to ensure that exceptions are properly handled, preventing unexpected program crashes.


4. Understanding Runtime Stack Mechanism

4.1 How Control Flows During Exception Handling

When an exception occurs in a Java program, the control flow shifts through the stack as the exception propagates. This process involves the JVM examining each stack frame, starting from the method where the exception was raised, to find an appropriate catch block to handle it. If no handler is found, the JVM eventually invokes the default exception handler, leading to abnormal termination of the program.


4.2 Steps in the Runtime Stack Process

4.2.1 Main Method Record Creation

  • When a Java program starts execution, the main method is invoked. The JVM creates a "stack frame" for the main method and pushes it onto the runtime stack.

  • This stack frame holds information such as local variables, method parameters, and the current state of execution for the main method.

4.2.2 Alpha Method Invocation

  • The main method calls the alpha() method. A new stack frame for alpha() is created and pushed onto the runtime stack, sitting on top of the main method's stack frame.

  • The execution of the code inside the alpha() method begins.

4.2.3 Exception Object Creation in alpha()

  • During the execution of the alpha() method, if an exception occurs (for example, a division by zero), the JVM creates an exception object (in this case, ArithmeticException).

  • This exception object is associated with the specific error that occurred, such as an attempt to divide by zero.

4.2.4 Propagation Through the Stack Hierarchy

  • The exception object is passed to the JVM, which first checks if the exception is handled within the alpha() method.

  • If alpha() does not have a catch block to handle this exception, the JVM automatically propagates the exception to the calling method, which in this case is the main() method.

  • The main() method’s stack frame is checked to see if it has a handler for the exception. If not, the JVM proceeds to the default exception handler.


4.3 Default Handler’s Role and Its Impact

  • If the exception is not caught within the alpha() method or the main() method, the JVM invokes the default exception handler.

  • The default handler terminates the program and prints a stack trace, which includes information about where the exception occurred (e.g., the line number in the code) and the type of exception (e.g., ArithmeticException: / by zero).


Code Example:

import java.util.Scanner;

class Alpha {
    void alpha() {
        System.out.println("Connection to calc is established");

        // Taking input from console
        Scanner scan = new Scanner(System.in);
        System.out.println("Enter num1 to divide:");
        int num1 = scan.nextInt();

        System.out.println("Enter num2 to divide:");
        int num2 = scan.nextInt();

        // Division operation that may cause an exception
        int res = num1 / num2;  // Division by zero exception if num2 = 0
        System.out.println("The result: " + res);

        System.out.println("Connection is terminated");
    }
}

public class Exception9 {
    public static void main(String[] args) {
        Alpha a = new Alpha();
        a.alpha();  // Calling alpha method
    }
}

Example Output (if num2 = 0):

Connection to calc is established
Enter num1 to divide:
100
Enter num2 to divide:
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Alpha.alpha(Exception9.java:46)
    at Exception9.main(Exception9.java:57)

Runtime Stack Analysis:

  1. Main Method Execution: The program starts execution from the main method.

  2. Alpha Method Call: The main method calls a.alpha(), creating a new stack frame for the alpha() method and placing it on top of the main method's stack frame.

  3. Division by Zero: Inside alpha(), when the division by zero occurs, the JVM creates an ArithmeticException object and searches for a matching catch block.

  4. No Handler in Alpha: Since there is no catch block in alpha(), the exception is propagated to the main() method.

  5. No Handler in Main: The main() method also does not have a handler for this exception, so the JVM proceeds to the default exception handler.

  6. Program Termination: The default exception handler terminates the program and prints the stack trace, indicating where the exception occurred.

In this case, the exception leads to abnormal termination because no catch block was present to handle it.


5. Exception Handling Techniques

Whenever there is an exception, we can choose to handle it in three main ways:

  1. Handle the exception using try-catch blocks.

  2. Duck the exception using the throws keyword.

  3. Re-throw the exception using throw, throws, try, catch, and finally.

Keywords in Exception Handling:

The following are the key keywords used in Java's exception handling mechanism:

  • try: Defines a block of code to monitor for exceptions.

  • catch: Catches exceptions thrown from the try block.

  • throw: Used to explicitly throw an exception.

  • throws: Declares that a method may throw an exception.

  • finally: Defines a block of code that will always execute after the try-catch block, regardless of whether an exception was thrown or not.


5.1 Handling Exception(try-catch) -

Step-by-step process:

  1. Control Flow:

    • The control starts in the main method.

    • From main, the beta1() method is called.

    • In beta1(), the alpha1() method is invoked.

    • In the alpha1() method, an exception may occur (e.g., division by zero).

  2. Exception Handling:

    • If an exception occurs, the JVM creates an exception object and looks for an appropriate handler.

    • If no handler is found in alpha1(), the exception is propagated to beta1().

    • If no handler is found in beta1(), the exception is propagated to the main() method.

    • The exception is caught in main(), and the program continues to run.


Table: Process of Exception Propagation

StepMethodAction
1. Method Callmain()The program starts execution in the main() method.
2. Method Invocationbeta1()The main() method calls beta1().
3. Method Invocationalpha1()beta1() calls the alpha1() method.
4. Exception Occursalpha1()An exception occurs (e.g., division by zero), and an exception object is created.
5. Propagation to Callerbeta1()The exception propagates to beta1() as it has no handler for the exception.
6. Propagation to Callermain()If no handler in beta1(), the exception propagates to main().
7. Exception Handledmain()The main() method catches the exception and handles it using catch.
8. Program Terminatesmain()After handling the exception, the program terminates.

Code Example:

import java.util.Scanner;

class Alpha1 {
    void alpha1() {
        System.out.println("Connection to calc is established");

        // Taking input from console
        Scanner scan = new Scanner(System.in);
        System.out.println("Enter num1 to divide:");
        int num1 = scan.nextInt();

        System.out.println("Enter num2 to divide:");
        int num2 = scan.nextInt();

        // Division operation that may cause an exception
        int res = num1 / num2;  // Division by zero exception if num2 = 0
        System.out.println("The result: " + res);

        System.out.println("Connection is terminated");
    }
}

class Beta1 {
    void beta1() {
        Alpha1 a = new Alpha1();
        a.alpha1();
    }
}

public class Exception10 {
    public static void main(String[] args) {
        try {
            Beta1 b = new Beta1();
            b.beta1();
        } catch (ArithmeticException ae) {
            System.out.println("Exception finally handled in main");
        }
    }
}

Explanation of Code and Process:

  • Main Method (main()):

    • The main() method begins execution and calls the beta1() method.
  • Beta Method (beta1()):

    • The beta1() method calls the alpha1() method, which contains the logic for dividing two numbers.
  • Alpha Method (alpha1()):

    • The user is prompted for two numbers. If the second number (num2) is 0, division by zero occurs.

    • The JVM throws an ArithmeticException when this happens.

  • Exception Handling:

    • In alpha1(): The exception is not handled, so it propagates up the call stack.

    • In beta1(): The exception is still not handled, so it propagates further up.

    • In main(): The main() method has a catch block that handles the ArithmeticException and prints the message: "Exception finally handled in main."


Output (when num2 = 0):

Connection to calc is established
Enter num1 to divide:
100
Enter num2 to divide:
0
Exception finally handled in main

Key Observations:

  1. Automatic Exception Propagation:

    • If an exception is not handled in a method, it is automatically propagated up the call stack (from alpha1()beta1()main()).
  2. Exception Handling in the main() Method:

    • The main() method successfully handles the exception, preventing the program from terminating abruptly and ensuring a graceful end.

5.2 Ducking Exceptions Using throws

Ducking Exceptions refers to the process of not handling an exception within the method where it occurs, but instead passing the responsibility of handling the exception to the calling method. This is done by using the throws keyword in the method signature. It is a way to inform the caller of the method that there is a possibility of an exception occurring, and it needs to handle it.

5.2.1 Concept of Ducking Exceptions with Examples

  • Ducking an exception means we avoid handling an exception inside a method and delegate the responsibility to the method that calls it. It is also known as propagating the exception.

  • This is particularly useful when we can't handle the exception at the current level but want to ensure that the exception is eventually caught and handled by the caller.

In Java, we achieve this by adding the throws keyword to the method signature, followed by the exception type(s) that the method might throw.

5.2.2 Code Example with Method Signature

Let’s understand the flow of exception handling using throws with the following code:

import java.util.Scanner;

class Alpha2 {
    // This method declares that it might throw an ArithmeticException
    void alpha2() throws ArithmeticException {
        // Connection to calculator application
        System.out.println("Connection to calc is established");

        // Taking input from the console
        Scanner scan = new Scanner(System.in);
        System.out.println("Enter num1 to divide:");
        int num1 = scan.nextInt();

        System.out.println("Enter num2 to divide:");
        int num2 = scan.nextInt();

        // Risky operation: Division that might throw ArithmeticException (if num2 == 0)
        int res = num1 / num2;
        System.out.println("The result: " + res);

        // Terminating the connection
        System.out.println("Connection is terminated");
    }
}

class Beta2 {
    // This method declares that it might throw an ArithmeticException
    void beta2() throws ArithmeticException {
        Alpha2 a = new Alpha2();
        a.alpha2();  // Call to alpha2, which may throw an exception
    }
}

public class Exception11 {
    public static void main(String[] args) {
        try {
            Beta2 b = new Beta2();
            b.beta2();  // Call to beta2, which may propagate the exception
        } catch (ArithmeticException ae) {
            // Handling the exception in the main method
            System.out.println("Exception finally handled in main");
        }
    }
}

5.2.3 Importance of Declaring Exceptions in Method Signature

  1. Passing Responsibility to the Caller:

    • When you declare an exception using throws, you are passing the responsibility of handling that exception to the caller. This is useful when you don't want or cannot handle the exception in the current method but want to make the caller aware of the possibility of it occurring.

    • For example, in the method alpha2(), the exception is not handled using a try-catch block. Instead, the method signature includes throws ArithmeticException, notifying the caller (in this case, beta2()) that an ArithmeticException may be thrown.

  2. Caution to the Caller:

    • By declaring exceptions in the method signature, the caller is forced to acknowledge the possibility of the exception and handle it appropriately.

    • In the example, beta2() calls alpha2(), and since alpha2() can throw an ArithmeticException, beta2() also declares throws ArithmeticException in its signature. Thus, the caller of beta2() (in the main method) will be aware of the potential exception and can handle it with a try-catch block.

  3. Avoiding Overly Complex Methods:

    • By using throws, the method doesn’t get cluttered with multiple try-catch blocks for every possible exception. Instead, it delegates the exception handling responsibility to higher-level methods that can manage these exceptions properly.
  4. Code Clarity and Maintainability:

    • Declaring exceptions explicitly in the method signature helps other developers or future maintainers of the code understand which exceptions might occur when calling a method. This makes the code more readable and maintainable.

Code Flow Explanation:

  1. Control Flow in Main:

    • First, control enters the main method.

    • The main method calls beta2(), which is responsible for invoking alpha2().

  2. Call to alpha2():

    • The method alpha2() executes and performs the division operation, which can throw an ArithmeticException if num2 is zero.

    • Instead of handling this exception within alpha2(), the method declares throws ArithmeticException in its signature, effectively passing the responsibility for handling the exception to the calling method (which is beta2()).

  3. Call to beta2():

    • The method beta2() calls alpha2(), but since alpha2() declares that it might throw an ArithmeticException, beta2() also declares throws ArithmeticException in its method signature. This ensures that any calling method (in this case, main()) is made aware of the potential exception.
  4. Handling Exception in Main:

    • When control returns to the main method, it catches the ArithmeticException thrown by alpha2() (through beta2()), and prints the message "Exception finally handled in main".

    • This is where the exception is actually handled, ensuring the program does not terminate unexpectedly.

Sample Output (when num2 is 0):

Connection to calc is established
Enter num1 to divide:
100
Enter num2 to divide:
0
Exception finally handled in main

Conclusion:

  • Ducking an exception using the throws keyword allows a method to pass the responsibility of handling an exception to the calling method, without taking the burden of handling it internally.

  • By declaring exceptions in the method signature, you are improving the clarity, maintainability, and responsibility management in your code.

  • Important Takeaways:

    • Always declare exceptions in the method signature using throws if the method can throw an exception, especially when you're not handling the exception inside the method.

    • The caller is then responsible for handling the exception, either by catching it or propagating it further up the call stack.

    • Ducking exceptions is particularly useful in scenarios where you want to allow the caller to decide how to handle specific exceptions, such as in the case of checked exceptions or runtime exceptions.

5.3 Re-Throwing Exceptions

5.3.1 Use of throw and throws for re-throwing exceptions.
5.3.2 Practical scenarios for re-throwing exceptions.


6. Keywords in Exception Handling

6.1 Overview of Java exception-handling keywords.
6.2 Purpose and usage of:

  • 6.2.1 try

  • 6.2.2 catch

  • 6.2.3 throw

  • 6.2.4 throws

  • 6.2.5 finally


7. Real-World Examples

7.1 Simulating ArithmeticException in division:

int result = num1 / num2;

7.2 Stack mechanism during exception propagation.
7.3 Handling user input errors gracefully.


8. Best Practices in Exception Handling

8.1 Writing clean and maintainable exception-handling code.
8.2 Avoiding common pitfalls like empty catch blocks.
8.3 Recommendations for unchecked exceptions:

  • Always handle critical exceptions.

  • Avoid unnecessary ducking of unchecked exceptions.


9. Advanced Concepts in Exception Handling

9.1 Custom exceptions: Defining and using them.
9.2 Exception chaining and its benefits.
9.3 Use of logging frameworks for better debugging.


10. When to Duck an Exception?

  • Usually, developers duck checked exceptions.

  • There are two types of exceptions in Java:

    1. Unchecked Exceptions:

      • The compiler will not force you to handle them.

      • They are not checked during compile time, only at runtime.

      • Developers identify risky code during development.

    2. Checked Exceptions:

      • These exceptions must be handled, or the compiler will not allow the code to run.

      • The risky code is checked during compilation.

      • The compiler ensures there’s no chance of abrupt termination.


11. Example of Checked Exception (Thread Sleep)

Example 1: Surround with Try-Catch

public class Exception12 {
    public static void main(String[] args) {
        try {
            Thread.sleep(4000); // risky code
        } catch (InterruptedException e) {
            e.printStackTrace(); // handles the exception
        }
    }
}

Example 2: Ducking an Exception

public class Exception12 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Before sleep");
        Thread.sleep(4000); // risky code
        System.out.println("After sleep"); // output displayed after 4 seconds
    }
}

12. Exception Propagation and Stack Hierarchy

  • If an exception occurs and is not handled in the current stack frame, it is propagated down the call stack to the caller.

  • Example: Alpha3 and Main

    1. Handler in Alpha3:

      • Exception handled locally; it does not propagate further.
    2. Handler in Main:

      • If not handled in Alpha3, the exception propagates to Main.

Example 1: Try-Catch in Alpha3

class Alpha3 {
    void alpha3() {
        System.out.println("Connection to Calc app established");
        try {
            int res = 10 / 0; // risky code
            System.out.println("The result is " + res);
        } catch (ArithmeticException e) {
            System.out.println("Exception handled in Alpha3");
        }
    }
}
public class Exception13 {
    public static void main(String[] args) {
        Alpha3 a = new Alpha3();
        a.alpha3();
    }
}

13. Re-Throwing an Exception

  • Re-throwing an exception propagates a handled exception to the caller.

  • Use the throw keyword inside a catch block.

Example: Re-Throwing with Finally Block

class Alpha4 {
    void alpha4() throws ArithmeticException {
        try {
            int res = 10 / 0; // risky code
            System.out.println("The result is " + res);
        } catch (ArithmeticException e) {
            System.out.println("Exception handled in Alpha4");
            throw e; // re-throws the exception
        } finally {
            System.out.println("Connection terminated");
        }
    }
}
public class Exception14 {
    public static void main(String[] args) {
        try {
            Alpha4 a = new Alpha4();
            a.alpha4();
        } catch (ArithmeticException e) {
            System.out.println("Exception handled in main");
        }
    }
}

14. Important Points

  1. The throw keyword is used to re-throw exceptions.

  2. It is written in the catch block.

  3. Statements after the throw keyword are not executed.

  4. Use the finally block for critical cleanup operations, ensuring it always executes.


15. Throw vs. Throws

KeywordPurposeUsageBehavior
throwRe-throws an exception to the callerInside methods or catch blocksSkips code below and propagates the error
throwsIndicates an exception may be thrownMethod signatureAlerts the caller to handle exceptions

16. Components of an Exception Object

  1. Exception Name:

    • Example: ArithmeticException
  2. Description:

    • Example: "Division by zero"
  3. Stack Trace:

    • Identifies the stack frame and line number where the exception occurred.

17. Three Methods Inside Catch Block We Usually Write

In Java, there are three commonly used methods inside a catch block to print information about exceptions. These methods are part of the Throwable class, which is the superclass of all errors and exceptions in Java.

Methods to Print Exception Information:

  1. getMessage(): Prints the description of the exception.

    • Example output: / by zero
  2. toString(): Prints the name of the exception class and its description.

    • Example output:

        java.lang.ArithmeticException: / by zero
      
  3. printStackTrace(): Prints the name of the exception, its description, and the complete stack trace showing where the exception occurred.

    • Example output:

        java.lang.ArithmeticException: / by zero
            at Alpha6.alpha6(Exception16.java:28)
            at Exception16.main(Exception16.java:45)
      

Example Program:

import java.util.Scanner;

class Alpha6 {
    void alpha6() {
        System.out.println("Connection to Calc app is established");
        try {
            Scanner scan = new Scanner(System.in);
            System.out.println("Enter the first number to divide:");
            int num1 = scan.nextInt();
            System.out.println("Enter the second number to divide:");
            int num2 = scan.nextInt();

            int res = num1 / num2; // Division operation
            System.out.println("The result is: " + res);
        } catch (ArithmeticException e) {
            System.out.println("Please provide valid input.");
            System.out.println("Description: " + e.getMessage());   // Method 1
            System.out.println("Details: " + e.toString());         // Method 2
            e.printStackTrace();                                   // Method 3
        }
    }
}

public class Exception16 {
    public static void main(String[] args) {
        Alpha6 alpha = new Alpha6();
        alpha.alpha6();
    }
}

Output (Example for Division by Zero):

Connection to Calc app is established
Enter the first number to divide:
100
Enter the second number to divide:
0
Please provide valid input.
Description: / by zero
Details: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
    at Alpha6.alpha6(Exception16.java:28)
    at Exception16.main(Exception16.java:45)

18. Finally Block Execution

The finally block in Java is used to ensure that certain critical code is executed, regardless of whether an exception is thrown or caught. It is commonly used to release resources like closing files or database connections.

Purpose of the finally Block:

  1. Resource Management: Ensures resources like files, database connections, or network sockets are closed.

  2. Guaranteed Execution: Runs whether an exception is handled or not, except in specific cases like System.exit().

Behavior of the finally Block:

Exception RaisedException HandledFinally Executed
No-Yes
YesYesYes
YesNoYes

Examples of Scenarios:

  • No Exception Occurs:

      try {
          System.out.println("Inside try");
      } catch (Exception e) {
          System.out.println("Inside catch");
      } finally {
          System.out.println("Inside finally");
      }
    

    Output:

      Inside try
      Inside finally
    
  • Exception Occurs and is Caught:

      try {
          System.out.println("Inside try");
          System.out.println(10 / 0); // ArithmeticException
      } catch (ArithmeticException e) {
          System.out.println("Inside catch");
      } finally {
          System.out.println("Inside finally");
      }
    

    Output:

      Inside try
      Inside catch
      Inside finally
    
  • Exception Occurs and is Not Caught:

      try {
          System.out.println("Inside try");
          System.out.println(10 / 0); // ArithmeticException
      } catch (NullPointerException e) {
          System.out.println("Inside catch");
      } finally {
          System.out.println("Inside finally");
      }
    

    Output:

      Inside try
      Inside finally
      Exception in thread "main" java.lang.ArithmeticException: / by zero
    

Nested try-catch and finally:

You can write a try-catch block inside a finally block to handle any exceptions that occur during resource cleanup.


Conclusion:

Understanding Exception Handling in Java is critical for writing robust and fault-tolerant applications. The knowledge gained from the discussed points forms a comprehensive foundation for exception handling. Here's the summary of the concepts:

  1. Definition and Need for Exception Handling: Exceptions are runtime anomalies that can disrupt normal program flow. Exception handling mechanisms (try-catch-finally) ensure graceful degradation of the program instead of abrupt termination.

  2. Hierarchy of Exceptions: The Java Exception Hierarchy illustrates that all exception classes inherit from the Throwable class. It further branches into:

    • Checked Exceptions: Must be declared or handled explicitly (e.g., IOException).

    • Unchecked Exceptions: Occur at runtime and don't require mandatory handling (e.g., NullPointerException).

  3. Three Exception Information Methods:

    • getMessage(): Provides a description of the exception.

    • toString(): Displays the exception class name and description.

    • printStackTrace(): Prints the exception's stack trace for debugging.

  4. finally Block Usage: Ensures resource cleanup, such as closing files or database connections, and always executes regardless of exception occurrence or handling.

  5. Exception Scenarios: Various cases like matching/non-matching catch blocks, nested try-catch blocks, and exceptions within finally blocks demonstrate Java's exception control flow.

  6. Unchecked vs. Checked Exceptions: Java classifies exceptions to emphasize compile-time checking for critical issues, while runtime exceptions may be ignored if appropriate.

  7. Partially Checked Exceptions: The Exception class acts as a parent to both checked and unchecked exceptions, making it partially checked. Subclasses like InterruptedException (checked) and NullPointerException (unchecked) illustrate this concept.

  8. Custom/User-Defined Exceptions: Java allows creating custom exception classes to handle specific scenarios not addressed by built-in exceptions.

  9. Nested Exception Handling: Writing nested try-catch blocks provides granularity in managing exceptions at different code levels, ensuring specific errors are handled where they occur.

  10. Practical Examples: Real-world implementations like division operations and file-handling code snippets demonstrate how to implement exception handling effectively.

  11. Abnormal vs. Normal Termination: Depending on the occurrence and handling of exceptions, programs can terminate gracefully (normal) or abruptly (abnormal). Proper handling avoids crashes.

  12. finally in Edge Cases: The finally block gets executed even in scenarios like non-matching catch blocks or exceptions raised within itself. This makes it indispensable for guaranteed cleanup.

  13. Exception Classes in java.lang Package: All standard exception classes belong to the java.lang package, eliminating the need for explicit import.

  14. Illustrations and Diagrams: Exception hierarchies and control flow diagrams aid in understanding relationships between exception types and program flow.

  15. Best Practices in Exception Handling:

    • Use specific exception classes over general ones (Exception or Throwable).

    • Always close resources using finally or try-with-resources.

    • Avoid using exceptions for control flow; handle only exceptional scenarios.

By grasping these concepts, Java developers can effectively manage errors and build reliable, maintainable applications. This comprehensive guide sets the stage for exploring more advanced topics like multi-threaded exception handling and custom error frameworks.

Chapter 3:Mastering Git: Installation, Architecture, and Collaborative Workflow for Developers

Chapter 34:Method Hiding Conclusion and Introduction to Exception Handling


Other Series:


Connect with Me
Stay updated with my latest posts and projects by following me on social media:

  • LinkedIn: Connect with me for professional updates and insights.

  • GitHub: Explore my repository and contributions to various projects.

  • LeetCode: Check out my coding practice and challenges.

Your feedback and engagement are invaluable. Feel free to reach out with questions, comments, or suggestions. Happy coding!


Rohit Gawande
Full Stack Java Developer | Blogger | Coding Enthusiast

0
Subscribe to my newsletter

Read articles from Rohit Gawande directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rohit Gawande
Rohit Gawande

🚀 Tech Enthusiast | Full Stack Developer | System Design Explorer 💻 Passionate About Building Scalable Solutions and Sharing Knowledge Hi, I’m Rohit Gawande! 👋I am a Full Stack Java Developer with a deep interest in System Design, Data Structures & Algorithms, and building modern web applications. My goal is to empower developers with practical knowledge, best practices, and insights from real-world experiences. What I’m Currently Doing 🔹 Writing an in-depth System Design Series to help developers master complex design concepts.🔹 Sharing insights and projects from my journey in Full Stack Java Development, DSA in Java (Alpha Plus Course), and Full Stack Web Development.🔹 Exploring advanced Java concepts and modern web technologies. What You Can Expect Here ✨ Detailed technical blogs with examples, diagrams, and real-world use cases.✨ Practical guides on Java, System Design, and Full Stack Development.✨ Community-driven discussions to learn and grow together. Let’s Connect! 🌐 GitHub – Explore my projects and contributions.💼 LinkedIn – Connect for opportunities and collaborations.🏆 LeetCode – Check out my problem-solving journey. 💡 "Learning is a journey, not a destination. Let’s grow together!" Feel free to customize or add more based on your preferences! 😊