Chapter 14: Errors & Exceptions

Introduction to Errors

No matter how smart we are, errors are our constant companions. With practice, we keep getting better at finding & correcting them.

There are three types of errors in Java:

1. Syntax Errors

When compiler finds something wrong with our program, it throws a syntax error.

int a = 9 // No semicolon - syntax error!
d = 4; // Variable not declared - syntax error!

2. Logical Errors

A logical error or a bug occurs when a program compiles and runs but does the wrong thing.

  • Message delivered wrongly
  • Wrong time of chats being displayed
  • Incorrect redirects
  • 3. Runtime Errors

    Java may sometimes encounter an error while the program is running. These are also called exceptions!

  • Division by zero
  • Array index out of bounds
  • Null pointer access
  • Who Encounters What?

    Error Type Encountered By When Example
    Syntax Errors Programmer Compile time Missing semicolon, undeclared variables
    Logical Errors Programmer During testing Wrong algorithm, incorrect calculations
    Runtime Errors Users Runtime Invalid input, resource constraints

    Runtime Error Example

    These are encountered due to circumstances like bad input and/or resource constraints.

    Example: User supplies "5" + "8" to a program which adds 2 numbers

    The program expects integers but receives strings, causing a runtime exception.

    Exceptions in Java

    An Exception is an event that occurs when a program is executed disrupting the normal flow of instructions.

    1. Checked Exceptions

    Compile time exceptions (Handled by Compiler)

  • Must be handled or declared
  • Checked at compile time
  • Examples: IOException, SQLException
  • 2. Unchecked Exceptions

    Runtime exceptions

  • Not checked at compile time
  • Occur during program execution
  • Examples: NullPointerException, ArithmeticException
  • Commonly Occurring Exceptions in Java

    1. NullPointerException

    Occurs when trying to access methods or fields of a null object.

    String str = null;
    int length = str.length(); // NullPointerException

    2. ArithmeticException

    Occurs during arithmetic operations like division by zero.

    int result = 10 / 0; // ArithmeticException

    3. ArrayIndexOutOfBoundsException

    Occurs when accessing array with invalid index.

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

    4. IllegalArgumentException

    Occurs when a method receives inappropriate arguments.

    Thread.sleep(-1000); // IllegalArgumentException

    5. NumberFormatException

    Occurs when trying to convert invalid string to number.

    int num = Integer.parseInt("abc"); // NumberFormatException

    Try-Catch Block in Java

    In Java, exceptions are managed using try-catch blocks.

    Basic Syntax:

    Try-Catch Syntax
    try {
        // Code to try
    } catch (Exception e) {
        // Code if exception occurs
    }
    BasicTryCatch.java
    public class BasicTryCatch {
        public static void main(String[] args) {
            try {
                int a = 10;
                int b = 0;
                int result = a / b; // This will cause ArithmeticException
                System.out.println("Result: " + result);
            } catch (ArithmeticException e) {
                System.out.println("Error: Cannot divide by zero!");
                System.out.println("Exception message: " + e.getMessage());
            }
            
            System.out.println("Program continues after exception handling");
            
            // Another example with array
            try {
                int[] numbers = {1, 2, 3, 4, 5};
                System.out.println("Element at index 10: " + numbers[10]);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Error: Array index out of bounds!");
                System.out.println("Exception: " + e.toString());
            }
            
            // Example with null pointer
            try {
                String text = null;
                int length = text.length();
            } catch (NullPointerException e) {
                System.out.println("Error: Null pointer exception occurred!");
                e.printStackTrace(); // Print full stack trace
            }
        }
    }

    Benefits of Try-Catch

  • Graceful Error Handling: Program doesn't crash abruptly
  • User-Friendly Messages: Display meaningful error messages
  • Program Continuation: Code after try-catch continues to execute
  • Debugging Information: Access to exception details
  • Handling Specific Exceptions

    In Java, we can handle specific exceptions by using multiple catch blocks.

    MultipleExceptions.java
    import java.io.*;
    import java.util.Scanner;
    
    public class MultipleExceptions {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            
            try {
                System.out.print("Enter first number: ");
                int num1 = Integer.parseInt(scanner.nextLine());
                
                System.out.print("Enter second number: ");
                int num2 = Integer.parseInt(scanner.nextLine());
                
                int result = num1 / num2;
                System.out.println("Result: " + result);
                
                // Array access example
                int[] arr = {1, 2, 3};
                System.out.println("Array element: " + arr[result]);
                
            } catch (NumberFormatException e) {
                System.out.println("Error: Please enter valid integers!");
                System.out.println("Details: " + e.getMessage());
                
            } catch (ArithmeticException e) {
                System.out.println("Error: Division by zero is not allowed!");
                System.out.println("Details: " + e.getMessage());
                
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Error: Array index out of bounds!");
                System.out.println("Details: " + e.getMessage());
                
            } catch (Exception e) {
                // Generic catch block for any other exceptions
                System.out.println("An unexpected error occurred: " + e.getMessage());
                e.printStackTrace();
            }
            
            System.out.println("Program execution completed.");
            scanner.close();
        }
    }

    Rules for Multiple Catch Blocks

  • Specific to General: More specific exceptions should be caught before general ones
  • Order Matters: Catch blocks are checked in order from top to bottom
  • Only One Executes: Only the first matching catch block executes
  • Parent-Child Hierarchy: Child exception classes must come before parent classes
  • CatchOrderExample.java
    public class CatchOrderExample {
        public static void main(String[] args) {
            // CORRECT ORDER - Specific to General
            try {
                int[] arr = new int[5];
                arr[10] = 50; // ArrayIndexOutOfBoundsException
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Array index error: " + e.getMessage());
            } catch (RuntimeException e) {
                System.out.println("Runtime error: " + e.getMessage());
            } catch (Exception e) {
                System.out.println("General error: " + e.getMessage());
            }
            
            // INCORRECT ORDER - Would cause compilation error
            /*
            try {
                // some code
            } catch (Exception e) {          // Too general - should be last
                System.out.println("General error");
            } catch (ArithmeticException e) { // Unreachable code - compilation error
                System.out.println("Arithmetic error");
            }
            */
        }
    }

    Exception Class Hierarchy

    All exceptions in Java inherit from the Throwable class.

    Exception Class Hierarchy

    Throwable
    Error
    Exception
    OutOfMemoryError
    RuntimeException
    IOException
    StackOverflowError
    NullPointerException
    ArithmeticException
    FileNotFoundException

    Important Exception Methods

    Method Description Example
    getMessage() Returns the detail message of the exception e.getMessage()
    toString() Returns exception class name and message e.toString()
    printStackTrace() Prints the stack trace to standard error e.printStackTrace()
    getStackTrace() Returns array of stack trace elements e.getStackTrace()
    getCause() Returns the cause of the exception e.getCause()
    ExceptionMethodsExample.java
    public class ExceptionMethodsExample {
        public static void main(String[] args) {
            try {
                // Intentionally cause an exception
                int result = 10 / 0;
            } catch (ArithmeticException e) {
                System.out.println("=== Exception Information ===");
                
                // getMessage() - Returns the detail message
                System.out.println("Message: " + e.getMessage());
                
                // toString() - Returns class name and message
                System.out.println("ToString: " + e.toString());
                
                // getClass() - Returns the exception class
                System.out.println("Class: " + e.getClass().getName());
                
                // printStackTrace() - Prints full stack trace
                System.out.println("\nStack Trace:");
                e.printStackTrace();
                
                // getStackTrace() - Returns stack trace as array
                System.out.println("\nStack Trace Elements:");
                StackTraceElement[] stackTrace = e.getStackTrace();
                for (StackTraceElement element : stackTrace) {
                    System.out.println("  " + element.toString());
                }
            }
            
            // Example with nested method calls
            try {
                methodA();
            } catch (Exception e) {
                System.out.println("\n=== Nested Method Exception ===");
                System.out.println("Exception in nested method: " + e.getMessage());
                e.printStackTrace();
            }
        }
        
        static void methodA() {
            methodB();
        }
        
        static void methodB() {
            methodC();
        }
        
        static void methodC() {
            // This will show the call stack
            String str = null;
            int length = str.length(); // NullPointerException
        }
    }

    Throw and Throws Keywords

    The throw and throws keywords are used for exception handling in Java.

    throw Keyword

  • Used to explicitly throw an exception
  • Used inside method body
  • Can throw only one exception at a time
  • Followed by an instance of exception
  • throw new ArithmeticException("Division by zero");

    throws Keyword

  • Used to declare exceptions that method might throw
  • Used in method signature
  • Can declare multiple exceptions
  • Followed by exception class names
  • public void method() throws IOException, SQLException
    ThrowExample.java
    public class ThrowExample {
        
        // Method that throws an exception explicitly
        public static void validateAge(int age) {
            if (age < 18) {
                throw new IllegalArgumentException("Age must be 18 or older. Provided: " + age);
            }
            System.out.println("Age is valid: " + age);
        }
        
        // Method that throws custom exception
        public static void divide(int a, int b) {
            if (b == 0) {
                throw new ArithmeticException("Cannot divide by zero! Divisor: " + b);
            }
            int result = a / b;
            System.out.println("Division result: " + result);
        }
        
        // Method that validates array index
        public static void accessArray(int[] arr, int index) {
            if (index < 0 || index >= arr.length) {
                throw new ArrayIndexOutOfBoundsException(
                    "Invalid index: " + index + ". Array length: " + arr.length);
            }
            System.out.println("Array element at index " + index + ": " + arr[index]);
        }
        
        public static void main(String[] args) {
            // Example 1: Age validation
            try {
                validateAge(25); // Valid age
                validateAge(15); // Invalid age - will throw exception
            } catch (IllegalArgumentException e) {
                System.out.println("Age validation error: " + e.getMessage());
            }
            
            // Example 2: Division
            try {
                divide(10, 2); // Valid division
                divide(10, 0); // Invalid division - will throw exception
            } catch (ArithmeticException e) {
                System.out.println("Division error: " + e.getMessage());
            }
            
            // Example 3: Array access
            try {
                int[] numbers = {1, 2, 3, 4, 5};
                accessArray(numbers, 2); // Valid index
                accessArray(numbers, 10); // Invalid index - will throw exception
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Array access error: " + e.getMessage());
            }
        }
    }
    ThrowsExample.java
    import java.io.*;
    
    public class ThrowsExample {
        
        // Method that declares it might throw IOException
        public static void readFile(String filename) throws IOException {
            FileReader file = new FileReader(filename);
            BufferedReader reader = new BufferedReader(file);
            
            String line = reader.readLine();
            System.out.println("First line: " + line);
            
            reader.close();
        }
        
        // Method that declares multiple exceptions
        public static void processData(String data) throws NumberFormatException, IllegalArgumentException {
            if (data == null || data.trim().isEmpty()) {
                throw new IllegalArgumentException("Data cannot be null or empty");
            }
            
            int number = Integer.parseInt(data); // May throw NumberFormatException
            System.out.println("Processed number: " + number);
        }
        
        // Method that calls other methods with throws
        public static void performOperations() throws IOException, NumberFormatException {
            readFile("data.txt");        // Must handle or declare IOException
            processData("123");          // Must handle or declare NumberFormatException
        }
        
        public static void main(String[] args) {
            // Handling IOException from readFile
            try {
                readFile("example.txt");
            } catch (IOException e) {
                System.out.println("File reading error: " + e.getMessage());
            }
            
            // Handling multiple exceptions from processData
            try {
                processData("abc");      // Will throw NumberFormatException
            } catch (NumberFormatException e) {
                System.out.println("Number format error: " + e.getMessage());
            } catch (IllegalArgumentException e) {
                System.out.println("Argument error: " + e.getMessage());
            }
            
            // Handling exceptions from performOperations
            try {
                performOperations();
            } catch (IOException e) {
                System.out.println("IO error in operations: " + e.getMessage());
            } catch (NumberFormatException e) {
                System.out.println("Number format error in operations: " + e.getMessage());
            }
        }
    }

    Creating Custom Exceptions

    CustomExceptionExample.java
    // Custom exception class
    class InsufficientBalanceException extends Exception {
        public InsufficientBalanceException(String message) {
            super(message);
        }
    }
    
    class BankAccount {
        private double balance;
        
        public BankAccount(double initialBalance) {
            this.balance = initialBalance;
        }
        
        public void withdraw(double amount) throws InsufficientBalanceException {
            if (amount > balance) {
                throw new InsufficientBalanceException(
                    "Insufficient balance. Available: " + balance + ", Requested: " + amount);
            }
            balance -= amount;
            System.out.println("Withdrawal successful. Remaining balance: " + balance);
        }
        
        public double getBalance() {
            return balance;
        }
    }
    
    public class CustomExceptionExample {
        public static void main(String[] args) {
            BankAccount account = new BankAccount(1000.0);
            
            try {
                account.withdraw(500);  // Valid withdrawal
                account.withdraw(600);  // Invalid - insufficient balance
            } catch (InsufficientBalanceException e) {
                System.out.println("Transaction failed: " + e.getMessage());
            }
        }
    }

    Finally Block

    The finally block contains code that is always executed, whether an exception occurs or not.

    It's typically used for cleanup operations like closing files, database connections, etc.

    Finally Block Syntax

    Finally Syntax
    try {
        // Code that may throw exception
    } catch (Exception e) {
        // Exception handling code
    } finally {
        // Code that always executes
    }
    FinallyExample.java
    import java.io.*;
    
    public class FinallyExample {
        
        public static void demonstrateFinally() {
            FileReader file = null;
            
            try {
                System.out.println("Opening file...");
                file = new FileReader("example.txt");
                
                // Simulate some file operations
                int data = file.read();
                System.out.println("File data read successfully");
                
            } catch (IOException e) {
                System.out.println("Error reading file: " + e.getMessage());
                
            } finally {
                // This block always executes
                System.out.println("Finally block executing...");
                
                if (file != null) {
                    try {
                        file.close();
                        System.out.println("File closed successfully");
                    } catch (IOException e) {
                        System.out.println("Error closing file: " + e.getMessage());
                    }
                }
            }
        }
        
        public static int demonstrateFinallyWithReturn() {
            try {
                System.out.println("In try block");
                return 1;
            } catch (Exception e) {
                System.out.println("In catch block");
                return 2;
            } finally {
                System.out.println("In finally block - this always executes");
                // Note: return in finally block would override try/catch return
            }
        }
        
        public static void demonstrateFinallyWithoutCatch() {
            try {
                System.out.println("Try block without catch");
                int result = 10 / 2; // No exception
                System.out.println("Result: " + result);
            } finally {
                System.out.println("Finally executes even without catch block");
            }
        }
        
        public static void main(String[] args) {
            System.out.println("=== Example 1: Finally with Exception ===");
            demonstrateFinally();
            
            System.out.println("\n=== Example 2: Finally with Return ===");
            int result = demonstrateFinallyWithReturn();
            System.out.println("Returned value: " + result);
            
            System.out.println("\n=== Example 3: Finally without Catch ===");
            demonstrateFinallyWithoutCatch();
            
            System.out.println("\n=== Example 4: Multiple Operations ===");
            performMultipleOperations();
        }
        
        public static void performMultipleOperations() {
            int[] numbers = {1, 2, 3, 4, 5};
            
            try {
                System.out.println("Starting operations...");
                
                // Operation 1: Array access
                System.out.println("Element at index 2: " + numbers[2]);
                
                // Operation 2: Division
                int result = 10 / 2;
                System.out.println("Division result: " + result);
                
                // Operation 3: Potential exception
                System.out.println("Element at index 10: " + numbers[10]); // Exception here
                
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Array error: " + e.getMessage());
                
            } catch (ArithmeticException e) {
                System.out.println("Math error: " + e.getMessage());
                
            } finally {
                System.out.println("Cleanup operations completed");
                System.out.println("Resources released");
            }
            
            System.out.println("Method execution completed");
        }
    }

    Important Rules for Finally Block

  • Always Executes: Finally block executes whether exception occurs or not
  • Cleanup Code: Perfect place for resource cleanup (files, connections, etc.)
  • Optional: Finally block is optional - you can have try-catch without finally
  • Execution Order: Finally executes after try or catch block
  • System.exit(): Finally won't execute if System.exit() is called
  • Return Override: Return statement in finally can override try/catch return
  • Try-with-Resources (Java 7+)

    A more elegant way to handle resources that implement AutoCloseable:

    TryWithResources.java
    import java.io.*;
    
    public class TryWithResources {
        public static void readFileWithResources(String filename) {
            // Resources declared in try() are automatically closed
            try (FileReader file = new FileReader(filename);
                 BufferedReader reader = new BufferedReader(file)) {
                
                String line = reader.readLine();
                System.out.println("File content: " + line);
                
            } catch (IOException e) {
                System.out.println("Error reading file: " + e.getMessage());
            }
            // No need for finally block - resources auto-closed
        }
        
        public static void main(String[] args) {
            readFileWithResources("example.txt");
        }
    }

    Practice Set

    Chapter 14 - Practice Set

    1. Syntax Error Demo

    Write a Java program that demonstrates syntax error.

    2. Runtime Error Demo

    Write a Java program that demonstrates runtime error (exception).

    3. Exception Handling

    Write a program that allows you to keep accessing an array until a valid index is given by the user.

    4. Multiple Catch Blocks

    Modify program 3 to include IllegalArgumentException as well as any other exception that occurs during runtime.

    5. Nested Try-Catch

    Write a program that demonstrates nested try-catch blocks.

    6. Custom Exception

    Create a custom exception class and demonstrate its usage.

    Solution Example: Array Access with Exception Handling

    Solution Example: Custom Exception

    ← Previous: Multithreading Back to Home →