Exception Handling Basics in Java

Exception handling is a crucial mechanism in Java that allows programs to handle runtime errors gracefully, maintaining normal program flow.

1. What are Exceptions?

Exceptions are events that disrupt the normal flow of program execution. Java provides a robust framework for handling these exceptions.

Exception Hierarchy

Throwable │ ├── Error (unchecked) │ ├── OutOfMemoryError │ ├── StackOverflowError │ └── VirtualMachineError │ └── Exception ├── RuntimeException (unchecked) │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ ├── IllegalArgumentException │ └── ArithmeticException │ └── Checked Exceptions ├── IOException ├── SQLException ├── FileNotFoundException └── ClassNotFoundException

2. Types of Exceptions

Checked Exceptions

  • Checked at compile-time
  • Must be handled using try-catch or declared with throws

Unchecked Exceptions

  • Checked at runtime
  • Subclasses of RuntimeException

Errors

  • Serious problems that applications should not try to catch
  • e.g., OutOfMemoryError, StackOverflowError

3. Basic Exception Handling Syntax

try-catch Block

public class BasicExceptionHandling { public static void main(String[] args) { try { // Code that might throw an exception int result = divide(10, 0); System.out.println("Result: " + result); } catch (ArithmeticException e) { // Handle the exception System.out.println("Error: Cannot divide by zero!"); System.out.println("Exception message: " + e.getMessage()); } System.out.println("Program continues normally..."); } public static int divide(int a, int b) { return a / b; } }

Multiple catch Blocks

public class MultipleCatchBlocks { public static void main(String[] args) { try { int[] numbers = {1, 2, 3}; // This will throw ArrayIndexOutOfBoundsException System.out.println(numbers[5]); // This would throw ArithmeticException (won't reach here) int result = 10 / 0; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Array index error: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Arithmetic error: " + e.getMessage()); } catch (Exception e) { System.out.println("General error: " + e.getMessage()); } } }

Multi-catch Block (Java 7+)

public class MultiCatchExample { public static void main(String[] args) { try { String str = null; System.out.println(str.length()); // NullPointerException int[] arr = new int[5]; arr[10] = 100; // ArrayIndexOutOfBoundsException } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { System.out.println("Caught exception: " + e.getClass().getSimpleName()); System.out.println("Message: " + e.getMessage()); } } }

4. finally Block

The finally block always executes, regardless of whether an exception occurred or not.

public class FinallyExample { public static void main(String[] args) { FileInputStream file = null; try { file = new FileInputStream("test.txt"); // Read file content System.out.println("File opened successfully"); } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); } finally { // This block always executes System.out.println("Finally block executed"); if (file != null) { try { file.close(); System.out.println("File closed"); } catch (IOException e) { System.out.println("Error closing file: " + e.getMessage()); } } } } }

5. try-with-resources (Java 7+)

Automatically closes resources that implement AutoCloseable interface.

public class TryWithResourcesExample { public static void main(String[] args) { // Resources are automatically closed try (FileInputStream input = new FileInputStream("test.txt"); FileOutputStream output = new FileOutputStream("output.txt")) { // Work with resources int data; while ((data = input.read()) != -1) { output.write(data); } System.out.println("File copied successfully"); } catch (IOException e) { System.out.println("IO Error: " + e.getMessage()); } // No need for finally block - resources are automatically closed } }

6. Throwing Exceptions

throw keyword

public class ThrowExample { public static void validateAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("Invalid age: " + age + ". Age must be between 0 and 150."); } System.out.println("Valid age: " + age); } public static void main(String[] args) { try { validateAge(25); // Valid validateAge(-5); // Throws exception validateAge(200); // Won't reach here } catch (IllegalArgumentException e) { System.out.println("Caught exception: " + e.getMessage()); } } }

throws keyword

public class ThrowsExample { // Method declares that it might throw IOException public static void readFile(String filename) throws IOException { if (filename == null || filename.isEmpty()) { throw new IOException("Filename cannot be null or empty"); } File file = new File(filename); if (!file.exists()) { throw new FileNotFoundException("File not found: " + filename); } // Read file content System.out.println("Reading file: " + filename); } public static void main(String[] args) { try { readFile("data.txt"); readFile(""); // This will throw IOException } catch (IOException e) { System.out.println("IO Exception: " + e.getMessage()); } } }

7. Custom Exceptions

Creating Custom Checked Exception

// Custom checked exception class InsufficientFundsException extends Exception { private double amount; private double balance; public InsufficientFundsException(double amount, double balance) { super("Insufficient funds: Attempted to withdraw $" + amount + " but balance is $" + balance); this.amount = amount; this.balance = balance; } public double getAmount() { return amount; } public double getBalance() { return balance; } }

Creating Custom Unchecked Exception

// Custom unchecked exception class InvalidAccountException extends RuntimeException { private String accountNumber; public InvalidAccountException(String accountNumber) { super("Invalid account number: " + accountNumber); this.accountNumber = accountNumber; } public String getAccountNumber() { return accountNumber; } }

Using Custom Exceptions

public class BankAccount { private String accountNumber; private double balance; public BankAccount(String accountNumber, double initialBalance) { if (accountNumber == null || accountNumber.length() != 8) { throw new InvalidAccountException(accountNumber); } this.accountNumber = accountNumber; this.balance = initialBalance; } public void withdraw(double amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException(amount, balance); } balance -= amount; System.out.println("Withdrawn: $" + amount + ", New balance: $" + balance); } public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Deposit amount must be positive"); } balance += amount; System.out.println("Deposited: $" + amount + ", New balance: $" + balance); } // Getters public double getBalance() { return balance; } public String getAccountNumber() { return accountNumber; } }

Testing Custom Exceptions

public class BankApp { public static void main(String[] args) { try { BankAccount account = new BankAccount("12345678", 1000); account.deposit(500); account.withdraw(200); account.withdraw(2000); // This will throw InsufficientFundsException } catch (InsufficientFundsException e) { System.out.println("Bank error: " + e.getMessage()); System.out.println("Attempted: $" + e.getAmount() + ", Available: $" + e.getBalance()); } catch (InvalidAccountException e) { System.out.println("Account error: " + e.getMessage()); } } }

8. Exception Propagation

public class ExceptionPropagation { public static void methodA() { System.out.println("In method A"); methodB(); System.out.println("Back to method A"); // Won't execute if exception occurs } public static void methodB() { System.out.println("In method B"); methodC(); System.out.println("Back to method B"); // Won't execute if exception occurs } public static void methodC() { System.out.println("In method C"); // This exception will propagate up the call stack throw new RuntimeException("Exception from method C"); } public static void main(String[] args) { try { methodA(); } catch (RuntimeException e) { System.out.println("Caught in main: " + e.getMessage()); System.out.println("Stack trace:"); e.printStackTrace(); } } }

9. Best Practices

1. Be Specific in Catch Blocks

// Good try { // code } catch (FileNotFoundException e) { // handle file not found } catch (IOException e) { // handle other IO issues } // Avoid - too broad try { // code } catch (Exception e) { // handles everything }

2. Don't Ignore Exceptions

// Bad - ignoring exception try { riskyOperation(); } catch (Exception e) { // empty catch block - DON'T DO THIS! } // Good - at least log it try { riskyOperation(); } catch (Exception e) { System.out.println("Operation failed: " + e.getMessage()); e.printStackTrace(); }

3. Use Finally for Cleanup

public void processFile(String filename) { FileInputStream file = null; try { file = new FileInputStream(filename); // process file } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } finally { if (file != null) { try { file.close(); } catch (IOException e) { System.out.println("Error closing file: " + e.getMessage()); } } } }

4. Prefer Try-with-resources

// Better approach with try-with-resources public void processFile(String filename) { try (FileInputStream file = new FileInputStream(filename)) { // process file } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } // File automatically closed }

10. Common Exception Handling Patterns

Retry Pattern

public class RetryPattern { public static void executeWithRetry(Runnable operation, int maxRetries) { int attempts = 0; while (attempts <= maxRetries) { try { operation.run(); return; // Success - exit method } catch (Exception e) { attempts++; if (attempts > maxRetries) { throw new RuntimeException("Operation failed after " + maxRetries + " attempts", e); } System.out.println("Attempt " + attempts + " failed, retrying..."); // Wait before retry try { Thread.sleep(1000 * attempts); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("Operation interrupted", ie); } } } } public static void main(String[] args) { executeWithRetry(() -> { // Simulate unreliable operation if (Math.random() < 0.7) { throw new RuntimeException("Temporary failure"); } System.out.println("Operation successful!"); }, 3); } }

Summary

  • try-catch: Handle exceptions locally
  • finally: Cleanup code that always executes
  • throw: Manually throw exceptions
  • throws: Declare exceptions a method might throw
  • Custom Exceptions: Create application-specific exceptions
  • Try-with-resources: Automatic resource management
  • Exception Propagation: Exceptions bubble up the call stack

Proper exception handling makes your code more robust, maintainable, and user-friendly by gracefully handling error conditions.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper