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