π¨ Java Exception Handling - Complete Guide β
A comprehensive guide to mastering Exception Handling in Java with examples, best practices, and advanced concepts_
Exception handling is not just about preventing crashesβit's about building resilient, maintainable, and user-friendly applications.
Remember: Great exception handling is invisible to users but invaluable to developers and system reliability!
π Table of Contents β
- π― Introduction
- π§ What is an Exception?
- ποΈ Exception Hierarchy
- π Types of Exceptions
- π οΈ Exception Handling Keywords
- πͺ Try-Catch Mechanism
- π Multiple Catch Blocks
- π§Ή Finally Block
- π― Throw vs Throws
- π§ Custom Exceptions
- π Exception Propagation
- π¨ Best Practices
- β‘ Advanced Concepts
- π‘ Real-World Examples
- π Summary
π― Introduction β
Exception handling is a fundamental concept in Java that allows programs to handle runtime errors gracefully. It's a mechanism to handle runtime errors such as ClassNotFoundException, IOException, SQLException, RemoteException, etc.
Why Exception Handling? β
- π‘οΈ Robust Applications: Prevents program crashes
- π― Error Recovery: Allows graceful error recovery
- π Better Debugging: Provides meaningful error messages
- π Program Continuity: Maintains normal program flow
π§ What is an Exception? β
An Exception is an unwanted or unexpected event that occurs during the execution of a program and disrupts the normal flow of instructions.
Key Characteristics: β
- π₯ Runtime occurrence
- π« Disrupts normal execution
- π― Can be handled programmatically
- π Provides stack trace information
Exception vs Error vs Bug β
ποΈ Exception Hierarchy β
Java's exception hierarchy is built around the Throwable
class:
Hierarchy Explanation: β
// Base class for all exceptions and errors
java.lang.Object
βββ java.lang.Throwable
βββ java.lang.Exception (Checked Exceptions)
β βββ IOException
β βββ ReflectiveOperationException
β β βββ ClassNotFoundException
β β βββ IllegalAccessException
β β βββ InstantiationException
β β βββ InvocationTargetException
β β βββ NoSuchFieldException
β β βββ NoSuchMethodException
β βββ CloneNotSupportedException
β βββ InterruptedException
β βββ java.lang.RuntimeException (Unchecked Exceptions)
β βββ ArithmeticException
β βββ ArrayStoreException
β βββ ClassCastException
β βββ ConcurrentModificationException
β βββ EnumConstantNotPresentException
β βββ IllegalArgumentException
β β βββ NumberFormatException
β βββ IllegalMonitorStateException
β βββ IllegalStateException
β βββ IndexOutOfBoundsException
β β βββ ArrayIndexOutOfBoundsException
β β βββ StringIndexOutOfBoundsException
β βββ NegativeArraySizeException
β βββ NullPointerException
β βββ SecurityException
β βββ TypeNotPresentException
β βββ UnsupportedOperationException
βββ java.lang.Error (System Errors)
βββ AssertionError
βββ LinkageError
β βββ BootstrapMethodError
β βββ ClassCircularityError
β βββ ClassFormatError
β βββ UnsupportedClassVersionError
β βββ ExceptionInInitializerError
β βββ IncompatibleClassChangeError
β β βββ AbstractMethodError
β β βββ IllegalAccessError
β β βββ InstantiationError
β β βββ NoSuchFieldError
β β βββ NoSuchMethodError
β βββ NoClassDefFoundError
β βββ UnsatisfiedLinkError
β βββ VerifyError
βββ ThreadDeath
βββ VirtualMachineError
βββ InternalError
βββ OutOfMemoryError
βββ StackOverflowError
βββ UnknownError
π Types of Exceptions β
Remember : Every exception occurs at runtime.
1. π Checked Exceptions β
Exceptions that are checked by compiler for smooth execution of program at runtime. Must be handled or declared.
Characteristics: β
- β Compile-time checking
- π Must be handled or declared
- π― Predictable and recoverable
- π Extend
Exception
class (but notRuntimeException
)
Examples: β
import java.io.*;
import java.sql.*;
public class CheckedExceptionExample {
// FileNotFoundException - Checked Exception
public void readFile(String fileName) throws IOException {
FileReader file = new FileReader(fileName); // May throw FileNotFoundException
BufferedReader reader = new BufferedReader(file);
String line = reader.readLine();
System.out.println(line);
reader.close();
}
// SQLException - Checked Exception
public void connectToDatabase() throws SQLException {
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testdb", "user", "password");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
conn.close();
}
// ClassNotFoundException - Checked Exception
public void loadClass(String className) throws ClassNotFoundException {
Class<?> clazz = Class.forName(className); // May throw ClassNotFoundException
System.out.println("Class loaded: " + clazz.getName());
}
}
2. β‘ Unchecked Exceptions (Runtime Exceptions) β
Exceptions that are not checked by compiler . Optional to handle.
Characteristics: β
- β No compile-time checking
- π― Optional to handle
- π Usually programming errors
- π Extend
RuntimeException
class
Examples: β
public class UncheckedExceptionExample {
// NullPointerException
public void demonstrateNPE() {
String str = null;
System.out.println(str.length()); // Throws NullPointerException
}
// ArrayIndexOutOfBoundsException
public void demonstrateArrayException() {
int[] array = {1, 2, 3};
System.out.println(array[5]); // Throws ArrayIndexOutOfBoundsException
}
// NumberFormatException
public void demonstrateNumberFormatException() {
String invalidNumber = "abc123";
int number = Integer.parseInt(invalidNumber); // Throws NumberFormatException
}
// IllegalArgumentException
public void demonstrateIllegalArgument() {
Thread.sleep(-1000); // Throws IllegalArgumentException
}
// ArithmeticException
public void demonstrateArithmeticException() {
int result = 10 / 0; // Throws ArithmeticException
}
}
3. π₯ Errors β
Serious problems that applications should not try to catch.
Characteristics: β
- π« Should not be caught
- π Usually fatal
- π System-level problems
- π Extend
Error
class
Examples: β
public class ErrorExample {
// OutOfMemoryError
public void demonstrateOutOfMemoryError() {
List<String> list = new ArrayList<>();
while (true) {
list.add("This will cause OutOfMemoryError");
}
}
// StackOverflowError
public void demonstrateStackOverflowError() {
demonstrateStackOverflowError(); // Infinite recursion
}
// Note: These are just demonstrations - don't actually run them!
}
Comparison Table: β
Aspect | Checked Exceptions | Unchecked Exceptions | Errors |
---|---|---|---|
π Compile-time Check | β Yes | β No | β No |
π οΈ Must Handle | β Yes | β οΈ Optional | π« No |
π― Recovery | β Possible | β Possible | β Not recommended |
π Extends | Exception | RuntimeException | Error |
π‘ Use Case | Expected conditions | Programming errors | System failures |
Exception Type | Category | Notes |
---|---|---|
Throwable | Partially Checked | Parent of all exceptions and errors; not fully checked itself |
Exception | Partially Checked | Parent of both checked and unchecked (Runtime) exceptions |
Subclasses of Exception (except RuntimeException ) | Fully Checked | Must be declared/handled (e.g., IOException , SQLException ) |
RuntimeException and its subclasses | Unchecked | Programmer errors (e.g., NullPointerException , ArithmeticException ) |
Error and its subclasses | Unchecked | Serious system errors (e.g., OutOfMemoryError , StackOverflowError ) |
π οΈ Exception Handling Keywords β
Java provides five keywords for exception handling:
1. π― try
β
try {
// Code that might throw an exception
int result = riskyOperation();
}
// Try to have as less code as possible in the try block
2. πͺ catch
β
catch (SpecificException e) {
// Handle specific exception
System.out.println("Handled: " + e.getMessage());
}
//e.getMessage() --> Prints only the description of the exception to the console.
//e.toString() --> Prints only the name and description of the exception to the console.
//e.printStackTrace() --> Prints all the information about the exception to the console.(name of exception, description and stackTrace as well)
3. π§Ή finally
β
finally {
// Always executed (cleanup code)
closeResources();
}
4. π throw
β
if (condition) {
throw new CustomException("Something went wrong!");
}
5. π’ throws
β
public void methodName() throws IOException, SQLException {
// Method that might throw exceptions
}
πͺ Try-Catch Mechanism β
The try-catch block is the core of exception handling:
Basic Syntax: β
public class TryCatchExample {
public static void main(String[] args) {
try {
// Risky code that might throw exception
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // This will throw exception
} catch (ArrayIndexOutOfBoundsException e) {
// Handle the specific exception
System.out.println("β Array index is out of bounds!");
System.out.println("π Error message: " + e.getMessage());
e.printStackTrace(); // Print stack trace
} catch (Exception e) {
// Handle any other exception
System.out.println("β Some other exception occurred: " + e.getMessage());
}
System.out.println("β
Program continues normally...");
}
}
Advanced Try-Catch Example: β
// Try with multiple catch block is highly recommended
import java.io.*;
import java.util.*;
public class AdvancedTryCatchExample {
public static void fileOperations() {
Scanner scanner = null;
FileWriter writer = null;
try {
// Multiple risky operations
scanner = new Scanner(new File("input.txt"));
writer = new FileWriter("output.txt");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
writer.write(line.toUpperCase() + "\n");
}
System.out.println("β
File operations completed successfully!");
} catch (FileNotFoundException e) {
System.err.println("β File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("β IO Error occurred: " + e.getMessage());
} catch (Exception e) {
System.err.println("β Unexpected error: " + e.getMessage());
e.printStackTrace();
} finally {
// Cleanup resources
if (scanner != null) {
scanner.close();
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
System.err.println("β Error closing writer: " + e.getMessage());
}
}
System.out.println("π§Ή Resources cleaned up");
}
}
}
//Note : The order of catch block is important, we should consider --> child to parent, but not parent to child;
Note :
class Test {
public static void main(String args[]) {
try {
System.out.println(10 / 0);
} catch (Exception e) {
System.out.println("Parent Exception");
} catch (ArithmeticException e) {
System.out.println("Child Exception");
}
}
}
// Note --> This will get compile time error, since parent exception will catch the exception, it does not makes sense to have ArithmeticException since we catch the exception from top to down.
Note :
class Test {
public stati void main(String args[]) {
try {
System.out.println("RK ROY");
} catch (IOEXception e) {}
}
}
// Compile-time error : you cannot write catch block if not needed for fully checked exception (IOException, InterruptedException, ..)
π§Ή Finally Block β
The finally
block always executes, regardless of whether an exception occurs or not.
Characteristics of Finally Block: β
- π Always Executes: Runs whether exception occurs or not
- π§Ή Cleanup Code: Perfect for resource cleanup
- π Placement: Must come after all catch blocks
- β οΈ Exception: Won't execute if JVM exits (
System.exit(0)
) - ποΈ Either
0
or any number other than0
can be put inside exit, the result will be same but its just indicate the different msg at the log level,0
--> normal termination where as any number other than0
is consider to be as abnormal termination.
Basic Finally Example: β
import java.io.*;
public class FinallyExample {
public static void demonstrateFinally() {
FileInputStream file = null;
try {
System.out.println("π Trying to open file...");
file = new FileInputStream("test.txt");
// Simulate some file operations
int data = file.read();
System.out.println("π Read data: " + data);
} catch (FileNotFoundException e) {
System.err.println("β File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("β IO Error: " + e.getMessage());
} finally {
// This ALWAYS executes
System.out.println("π§Ή Finally block executing...");
if (file != null) {
try {
file.close();
System.out.println("β
File closed successfully");
} catch (IOException e) {
System.err.println("β Error closing file: " + e.getMessage());
}
}
System.out.println("π Finally block completed");
}
System.out.println("π Program continues...");
}
}
Finally vs Return Statement: β
public class FinallyVsReturn {
public static int demonstrateFinallyWithReturn() {
try {
System.out.println("π In try block");
return 1; // This return is "postponed"
} catch (Exception e) {
System.out.println("β In catch block");
return 2;
} finally {
System.out.println("π§Ή Finally block - always executes!");
// This executes even before return
}
// This line is unreachable
}
// What gets returned? Let's see:
public static void testFinallyReturn() {
int result = demonstrateFinallyWithReturn();
System.out.println("π’ Returned value: " + result); // Will print 1
}
}
π final
, finally
, and finalize()
β
In Java, final
, finally
, and finalize()
look similar but serve very different purposes.
π€ final
(Keyword) β
- Used for variables, methods, and classes.
- Prevents modification, overriding, or inheritance.
Examples:
// Final variable (constant)
final int x = 10;
// x = 20; β Error
// Final method (cannot be overridden)
class A {
final void show() {
System.out.println("Hello");
}
}
class B extends A {
// void show() {} β Error
}
// Final class (cannot be inherited)
final class C {}
// class D extends C {} β Error
π«± finally
(Block) β
- Used with try-catch in exception handling.
- Purpose: Ensures that cleanup code (like closing files, releasing resources) always executes.
- Guarantee: Executes whether an exception occurs or not.
- Exception: Will not run only in rare cases like
System.exit(0)
or sudden JVM crash.
Example: β
try {
int x = 10 / 0; // Exception occurs here
} catch (ArithmeticException e) {
System.out.println("Exception handled");
} finally {
System.out.println("Finally block executed");
}
π«° finalize()
(Method) β
- Defined in the
java.lang.Object
class. - Purpose: Called by the Garbage Collector (GC) before an object is destroyed.
- Can be overridden to perform cleanup tasks (like releasing resources, closing connections).
- Important: Execution of
finalize()
is not guaranteed β depends on when/if the GC runs. - In modern Java, it is considered deprecated and not recommended for resource management (use
try-with-resources
instead).
Example: β
class Test {
@Override
protected void finalize() {
System.out.println("Object is destroyed");
}
}
public class Main {
public static void main(String[] args) {
Test t = new Test();
t = null; // Make object eligible for GC
System.gc(); // Request garbage collection
}
}
Try-With-Resources (Java 7+): β
Automatic resource management - a modern alternative to finally:
import java.io.*;
import java.nio.file.*;
public class TryWithResourcesExample {
// Old way with finally
public static void oldWayWithFinally() {
BufferedReader reader = null;
try {
reader = Files.newBufferedReader(Paths.get("example.txt"));
String line = reader.readLine();
System.out.println("π Read: " + line);
} catch (IOException e) {
System.err.println("β Error: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.err.println("β Error closing: " + e.getMessage());
}
}
}
}
// β
New way with try-with-resources
public static void newWayWithTryWithResources() {
try (BufferedReader reader = Files.newBufferedReader(Paths.get("example.txt"))) {
String line = reader.readLine();
System.out.println("π Read: " + line);
// reader.close() is automatically called!
} catch (IOException e) {
System.err.println("β Error: " + e.getMessage());
}
// Much cleaner and safer!
}
// Multiple resources
public static void multipleResources() {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
// All resources will be closed automatically
String line;
while ((line = reader.readLine()) != null) {
fos.write(line.getBytes());
}
} catch (IOException e) {
System.err.println("β Error: " + e.getMessage());
}
}
}
// try (r1; r2; r3) --> Here only only those resources are allowed that implements autclosable interface introduced in java v1.7;
// try (r1; r2; r3) --> All the reference variable of the resources are implicitly final.
// try with resources but without catch is totally valid from java v1.7 onwards.
π― Throw vs Throws β
Two important keywords that are often confused:
π throw
Keyword β
- Purpose: Explicitly throw an exception
- Usage: Inside method body
- Syntax:
throw new ExceptionType("message")
- Effect: Immediately terminates current method, there cannot be any other statement below the throw statement.
- Scope : Only allowed to use for throwable types.
π’ throws
Keyword β
- Used to deligate the responsibility of handling the exception to the caller (JVM or Method).
- It is used only to convince the compiler and its usage does not prevent abnormal termination of the program.
- Purpose: Declare that method might throw exceptions
- Usage: In method signature
- Syntax:
methodName() throws ExceptionType1, ExceptionType2
- Effect: Passes responsibility to caller
- Scope : Should try to use only for checked exceptions.
Comparison Table: β
Aspect | throw | throws |
---|---|---|
π Location | Inside method body | Method signature |
π― Purpose | Throw exception | Declare exception |
π’ Count | One exception at a time | Multiple exceptions |
π Execution | Terminates method | Just declaration |
π Syntax | throw new Exception() | throws Exception |
Throw Examples: β
public class ThrowExample {
public static void validateAge(int age) {
if (age < 0) {
// β Explicitly throwing an exception
throw new IllegalArgumentException("β Age cannot be negative: " + age);
}
if (age > 150) {
throw new IllegalArgumentException("β Age seems unrealistic: " + age);
}
System.out.println("β
Valid age: " + age);
}
public static void withdrawMoney(double balance, double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("π° Amount must be positive");
}
if (amount > balance) {
throw new RuntimeException("π³ Insufficient balance. Available: " + balance + ", Requested: " + amount);
}
System.out.println("β
Withdrawal successful: $" + amount);
}
public static void divideNumbers(int a, int b) {
if (b == 0) {
throw new ArithmeticException("β Division by zero is not allowed!");
}
int result = a / b;
System.out.println("π’ Result: " + a + " / " + b + " = " + result);
}
}
Throws Examples: β
import java.io.*;
import java.sql.*;
import java.net.*;
public class ThrowsExample {
// π’ Method declares it might throw IOException
public static void readFile(String fileName) throws IOException {
FileReader file = new FileReader(fileName); // May throw FileNotFoundException
BufferedReader reader = new BufferedReader(file);
String line = reader.readLine(); // May throw IOException
System.out.println("π Read: " + line);
reader.close(); // May throw IOException
}
// π’ Method declares multiple exceptions
public static void databaseOperation() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver"); // ClassNotFoundException
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testdb", "user", "password"); // SQLException
Statement stmt = conn.createStatement(); // SQLException
ResultSet rs = stmt.executeQuery("SELECT * FROM users"); // SQLException
conn.close();
}
// π’ Method that calls other methods with throws
public static void performOperations() {
try {
readFile("data.txt"); // Must handle IOException
databaseOperation(); // Must handle SQLException and ClassNotFoundException
} catch (IOException e) {
System.err.println("π File error: " + e.getMessage());
} catch (SQLException e) {
System.err.println("ποΈ Database error: " + e.getMessage());
} catch (ClassNotFoundException e) {
System.err.println("π Driver not found: " + e.getMessage());
}
}
// π’ Can also re-throw exceptions
public static void processFile(String fileName) throws IOException {
try {
readFile(fileName);
} catch (IOException e) {
// Log the error
System.err.println("π Logging error: " + e.getMessage());
// Re-throw the same exception
throw e;
}
}
}
Combining Throw and Throws: β
public class ThrowAndThrowsExample {
// Method that throws custom exceptions
public static void authenticateUser(String username, String password)
throws AuthenticationException {
if (username == null || username.isEmpty()) {
// Using 'throw' to throw an exception
throw new AuthenticationException("π€ Username cannot be empty");
}
if (password == null || password.length() < 8) {
// Using 'throw' to throw an exception
throw new AuthenticationException("π Password must be at least 8 characters");
}
// Simulate authentication logic
if (!"admin".equals(username) || !"password123".equals(password)) {
throw new AuthenticationException("β Invalid credentials");
}
System.out.println("β
Authentication successful!");
}
// Custom exception class
static class AuthenticationException extends Exception {
public AuthenticationException(String message) {
super(message);
}
}
// Method that handles the declared exception
public static void loginUser(String username, String password) {
try {
authenticateUser(username, password); // Must handle AuthenticationException
System.out.println("π User logged in successfully!");
} catch (AuthenticationException e) {
System.err.println("π« Login failed: " + e.getMessage());
}
}
}
π Exception Propagation β
Exception propagation is the process by which an exception is passed from the method where it occurs to the method that calls it.
How Exception Propagation Works: β
Exception Propagation Examples: β
public class ExceptionPropagationDemo {
// Method that throws an exception (not handled here)
public static void method3() {
System.out.println("π Entering method3()");
// This will throw ArithmeticException
int result = 10 / 0; // π₯ Exception occurs here
System.out.println("β
This line won't execute");
}
// Method that calls method3 (not handling exception)
public static void method2() {
System.out.println("π Entering method2()");
method3(); // Exception propagates from here
System.out.println("β
This line won't execute");
}
// Method that calls method2 (not handling exception)
public static void method1() {
System.out.println("π Entering method1()");
method2(); // Exception propagates from here
System.out.println("β
This line won't execute");
}
// Main method - final opportunity to handle
public static void main(String[] args) {
System.out.println("π Starting program...");
try {
method1(); // Exception will propagate to here
} catch (ArithmeticException e) {
System.err.println("β Caught in main: " + e.getMessage());
System.err.println("π Exception originated in method3");
e.printStackTrace();
}
System.out.println("π Program continues after handling...");
}
}
Propagation with Checked Exceptions: β
import java.io.*;
public class CheckedExceptionPropagation {
// Method that throws checked exception
public static void readFileMethod() throws IOException {
System.out.println("π Attempting to read file...");
FileReader file = new FileReader("nonexistent.txt"); // FileNotFoundException
BufferedReader reader = new BufferedReader(file);
reader.readLine();
reader.close();
}
// Method that calls readFileMethod - must declare or handle
public static void processFileMethod() throws IOException {
System.out.println("π Processing file...");
readFileMethod(); // Must declare 'throws IOException' or handle it
}
// Method that decides to handle the exception
public static void handleFileOperations() {
try {
processFileMethod();
System.out.println("β
File operations completed");
} catch (FileNotFoundException e) {
System.err.println("β File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("β IO error occurred: " + e.getMessage());
}
}
public static void main(String[] args) {
handleFileOperations();
}
}
Stopping Exception Propagation: β
public class StopPropagationExample {
public static void riskyMethod() throws Exception {
throw new Exception("π₯ Something went wrong in riskyMethod!");
}
// This method handles the exception and stops propagation
public static void intermediateMethod() {
try {
System.out.println("π Calling risky method...");
riskyMethod();
} catch (Exception e) {
System.err.println("β Exception caught and handled in intermediateMethod");
System.err.println("π Message: " + e.getMessage());
// Exception is handled here - propagation stops
// We can choose to recover gracefully
System.out.println("π Recovering from error...");
}
System.out.println("β
intermediateMethod completed normally");
}
public static void callerMethod() {
System.out.println("π Entering callerMethod");
intermediateMethod(); // No exception propagates from here
System.out.println("β
callerMethod completed normally");
}
public static void main(String[] args) {
callerMethod();
System.out.println("π Program finished successfully");
}
}
Exception Chain (Cause and Effect): β
public class ExceptionChainExample {
public static void lowLevelOperation() throws Exception {
throw new IOException("πΎ Low-level IO error occurred");
}
public static void businessLogic() throws BusinessException {
try {
lowLevelOperation();
} catch (IOException e) {
// Wrap the original exception with business context
throw new BusinessException(
"π’ Business operation failed",
"BUS-001",
e); // Original exception as cause
}
}
// Custom business exception with cause chaining
static class BusinessException extends Exception {
private String errorCode;
public BusinessException(String message, String errorCode, Throwable cause) {
super(message, cause); // Chain the cause
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
public static void main(String[] args) {
try {
businessLogic();
} catch (BusinessException e) {
System.err.println("β Business Error: " + e.getMessage());
System.err.println("π Error Code: " + e.getErrorCode());
// Access the original cause
Throwable cause = e.getCause();
if (cause != null) {
System.err.println("π Root cause: " + cause.getMessage());
System.err.println("π·οΈ Root cause type: " + cause.getClass().getSimpleName());
}
System.err.println("\nπ Full stack trace:");
e.printStackTrace();
}
}
}
π¨ Best Practices β
Following best practices ensures robust and maintainable exception handling:
1. π― Be Specific with Exceptions β
// β BAD - Too generic
public void processUser(String userId) throws Exception {
if (userId == null) {
throw new Exception("User ID is null");
}
// ...
}
// β
GOOD - Specific exceptions
public void processUser(String userId) throws ValidationException, UserNotFoundException {
if (userId == null) {
throw new ValidationException("User ID cannot be null", "userId", null);
}
if (!userExists(userId)) {
throw new UserNotFoundException("User not found with ID: " + userId);
}
// ...
}
2. π Provide Meaningful Error Messages β
// β BAD - Vague error message
if (age < 0) {
throw new IllegalArgumentException("Invalid age");
}
// β
GOOD - Descriptive error message
if (age < 0) {
throw new IllegalArgumentException(
String.format("Age cannot be negative. Provided value: %d. " +
"Please provide a valid age between 0 and 150.", age));
}
3. π§Ή Always Clean Up Resources β
// β BAD - Resource leak potential
public void processFile(String fileName) throws IOException {
FileInputStream fis = new FileInputStream(fileName);
// If exception occurs here, file won't be closed
processData(fis);
fis.close();
}
// β
GOOD - Using try-with-resources
public void processFile(String fileName) throws IOException {
try (FileInputStream fis = new FileInputStream(fileName)) {
processData(fis);
// File automatically closed even if exception occurs
}
}
// β
GOOD - Using finally block
public void processFileAlternative(String fileName) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileName);
processData(fis);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// Log the error but don't throw
System.err.println("Error closing file: " + e.getMessage());
}
}
}
}
4. π« Don't Swallow Exceptions β
// β BAD - Swallowing exception
try {
riskyOperation();
} catch (Exception e) {
// Silent failure - very bad!
}
// β BAD - Only printing stack trace
try {
riskyOperation();
} catch (Exception e) {
e.printStackTrace(); // Not enough!
}
// β
GOOD - Proper error handling
try {
riskyOperation();
} catch (SpecificException e) {
// Log the error
logger.error("Failed to perform risky operation", e);
// Take appropriate action
notifyAdministrator(e);
// Re-throw if necessary or handle gracefully
throw new ServiceException("Service temporarily unavailable", e);
}
5. π Use Exception Hierarchy Properly β
// β
GOOD - Proper exception hierarchy
public abstract class PaymentException extends Exception {
private String transactionId;
public PaymentException(String message, String transactionId) {
super(message);
this.transactionId = transactionId;
}
public String getTransactionId() { return transactionId; }
}
class InsufficientFundsException extends PaymentException {
public InsufficientFundsException(String message, String transactionId) {
super(message, transactionId);
}
}
class CardExpiredException extends PaymentException {
public CardExpiredException(String message, String transactionId) {
super(message, transactionId);
}
}
// Now you can catch specific exceptions or the general PaymentException
try {
processPayment(amount, cardNumber);
} catch (InsufficientFundsException e) {
// Handle insufficient funds specifically
requestAdditionalFunds(e.getTransactionId());
} catch (CardExpiredException e) {
// Handle expired card specifically
requestNewCard(e.getTransactionId());
} catch (PaymentException e) {
// Handle any other payment-related exception
logPaymentError(e.getTransactionId(), e.getMessage());
}
6. π Log Before Re-throwing β
public class LoggingBestPractice {
private static final Logger logger = LoggerFactory.getLogger(LoggingBestPractice.class);
public void processOrder(Order order) throws OrderProcessingException {
try {
validateOrder(order);
chargePayment(order);
updateInventory(order);
} catch (ValidationException e) {
// Log with context before re-throwing
logger.error("Order validation failed for order ID: {} - {}",
order.getId(), e.getMessage(), e);
throw new OrderProcessingException("Invalid order data", e);
} catch (PaymentException e) {
logger.error("Payment processing failed for order ID: {} - {}",
order.getId(), e.getMessage(), e);
throw new OrderProcessingException("Payment processing failed", e);
} catch (InventoryException e) {
logger.error("Inventory update failed for order ID: {} - {}",
order.getId(), e.getMessage(), e);
throw new OrderProcessingException("Inventory update failed", e);
}
}
}
7. π¨ Create Custom Exceptions Judiciously β
// β
GOOD - Custom exception adds value
public class PasswordValidationException extends Exception {
private final List<String> failedCriteria;
private final PasswordStrength currentStrength;
public PasswordValidationException(String message,
List<String> failedCriteria,
PasswordStrength currentStrength) {
super(message);
this.failedCriteria = new ArrayList<>(failedCriteria);
this.currentStrength = currentStrength;
}
public List<String> getFailedCriteria() {
return new ArrayList<>(failedCriteria);
}
public PasswordStrength getCurrentStrength() {
return currentStrength;
}
@Override
public String getMessage() {
StringBuilder sb = new StringBuilder(super.getMessage());
sb.append("\nFailed criteria: ").append(failedCriteria);
sb.append("\nCurrent strength: ").append(currentStrength);
return sb.toString();
}
}
enum PasswordStrength {
WEAK, MODERATE, STRONG, VERY_STRONG
}
8. β οΈ Validate Early and Often β
public class ValidationBestPractice {
public void createUser(String username, String email, int age) {
// β
Validate immediately at method entry
validateUsername(username);
validateEmail(email);
validateAge(age);
// Now proceed with business logic
User user = new User(username, email, age);
saveUser(user);
}
private void validateUsername(String username) {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("π€ Username cannot be null or empty");
}
if (username.length() < 3) {
throw new IllegalArgumentException(
String.format("π€ Username must be at least 3 characters. Provided: %d", username.length()));
}
if (!username.matches("^[a-zA-Z0-9_]+$")) {
throw new IllegalArgumentException(
"π€ Username can only contain letters, numbers, and underscores");
}
}
private void validateEmail(String email) {
if (email == null) {
throw new IllegalArgumentException("π§ Email cannot be null");
}
if (!email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
throw new IllegalArgumentException("π§ Invalid email format: " + email);
}
}
private void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException(
String.format("π Age cannot be negative. Provided: %d", age));
}
if (age > 150) {
throw new IllegalArgumentException(
String.format("π Age seems unrealistic. Provided: %d", age));
}
}
}
π Best Practices Summary: β
Practice | β Don't | β Do |
---|---|---|
Exception Types | Use generic Exception | Use specific exception types |
Error Messages | "Error occurred" | "File 'config.xml' not found in path '/etc/app/'" |
Resource Management | Manual cleanup in finally | Try-with-resources or proper finally blocks |
Exception Handling | Swallow exceptions silently | Log, handle appropriately, or re-throw |
Custom Exceptions | Create for every error | Create when they add meaningful value |
Validation | Validate deep in logic | Validate early at method entry |
Logging | Log and re-throw same level | Log at boundary, re-throw with context |
Stack Traces | Print to console | Use proper logging framework |
β‘ Advanced Concepts β
1. π Exception Translation β
Convert low-level exceptions to high-level business exceptions:
public class ExceptionTranslationExample {
// Low-level data access layer
public class UserRepository {
public User findById(String userId) throws DataAccessException {
try {
// Database operation that might throw SQLException
Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
stmt.setString(1, userId);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
return null;
}
return mapResultSetToUser(rs);
} catch (SQLException e) {
// Translate SQLException to DataAccessException
throw new DataAccessException(
"Failed to retrieve user with ID: " + userId, e);
}
}
private Connection getConnection() throws SQLException {
// Database connection logic
return DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
}
private User mapResultSetToUser(ResultSet rs) throws SQLException {
// Mapping logic
return new User(rs.getString("id"), rs.getString("name"), rs.getString("email"));
}
}
// High-level service layer
public class UserService {
private UserRepository userRepository = new UserRepository();
public User getUser(String userId) throws UserServiceException {
try {
User user = userRepository.findById(userId);
if (user == null) {
throw new UserNotFoundException("User not found with ID: " + userId);
}
return user;
} catch (DataAccessException e) {
// Translate data access exception to service exception
throw new UserServiceException(
"Unable to retrieve user information", e);
}
}
}
// Exception classes
class DataAccessException extends Exception {
public DataAccessException(String message, Throwable cause) {
super(message, cause);
}
}
class UserServiceException extends Exception {
public UserServiceException(String message, Throwable cause) {
super(message, cause);
}
}
class UserNotFoundException extends UserServiceException {
public UserNotFoundException(String message) {
super(message, null);
}
}
class User {
private String id, name, email;
public User(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters...
}
}
2. π Retry Mechanisms β
Implement retry logic for transient failures:
public class RetryMechanismExample {
public class RetryableService {
private static final Logger logger = LoggerFactory.getLogger(RetryableService.class);
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
public String callExternalService(String request) throws ServiceException {
return executeWithRetry(() -> {
// Simulate external service call
if (Math.random() < 0.7) { // 70% chance of failure
throw new TransientServiceException("Service temporarily unavailable");
}
return "Success: " + request;
});
}
private <T> T executeWithRetry(RetryableOperation<T> operation) throws ServiceException {
int attempts = 0;
Exception lastException = null;
while (attempts < MAX_RETRIES) {
try {
attempts++;
logger.info("π Attempt {} of {}", attempts, MAX_RETRIES);
return operation.execute();
} catch (TransientServiceException e) {
lastException = e;
logger.warn("β οΈ Attempt {} failed: {}", attempts, e.getMessage());
if (attempts < MAX_RETRIES) {
try {
logger.info("π΄ Waiting {}ms before retry...", RETRY_DELAY_MS);
Thread.sleep(RETRY_DELAY_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new ServiceException("Retry interrupted", ie);
}
}
} catch (PermanentServiceException e) {
// Don't retry permanent failures
logger.error("β Permanent failure, not retrying: {}", e.getMessage());
throw new ServiceException("Permanent service failure", e);
}
}
// All retries exhausted
logger.error("π₯ All {} attempts failed", MAX_RETRIES);
throw new ServiceException(
String.format("Service call failed after %d attempts", MAX_RETRIES),
lastException);
}
@FunctionalInterface
private interface RetryableOperation<T> {
T execute() throws TransientServiceException, PermanentServiceException;
}
}
// Exception hierarchy for retry logic
abstract class ServiceException extends Exception {
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
}
class TransientServiceException extends RuntimeException {
public TransientServiceException(String message) {
super(message);
}
}
class PermanentServiceException extends RuntimeException {
public PermanentServiceException(String message) {
super(message);
}
}
}
3. π Circuit Breaker Pattern β
Prevent cascade failures by implementing circuit breaker:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class CircuitBreakerExample {
public class CircuitBreaker {
private final int failureThreshold;
private final long timeoutDuration;
private final int successThreshold;
private final AtomicInteger failureCount = new AtomicInteger(0);
private final AtomicInteger successCount = new AtomicInteger(0);
private final AtomicLong lastFailureTime = new AtomicLong(0);
private volatile CircuitState state = CircuitState.CLOSED;
public CircuitBreaker(int failureThreshold, long timeoutDuration, int successThreshold) {
this.failureThreshold = failureThreshold;
this.timeoutDuration = timeoutDuration;
this.successThreshold = successThreshold;
}
public <T> T execute(CircuitBreakerOperation<T> operation) throws CircuitBreakerException {
if (state == CircuitState.OPEN) {
if (System.currentTimeMillis() - lastFailureTime.get() > timeoutDuration) {
state = CircuitState.HALF_OPEN;
successCount.set(0);
System.out.println("π Circuit breaker moving to HALF_OPEN state");
} else {
throw new CircuitBreakerException("π« Circuit breaker is OPEN - calls not allowed");
}
}
try {
T result = operation.execute();
onSuccess();
return result;
} catch (Exception e) {
onFailure();
throw new CircuitBreakerException("Operation failed", e);
}
}
private void onSuccess() {
failureCount.set(0);
if (state == CircuitState.HALF_OPEN) {
int currentSuccessCount = successCount.incrementAndGet();
if (currentSuccessCount >= successThreshold) {
state = CircuitState.CLOSED;
System.out.println("β
Circuit breaker moving to CLOSED state");
}
}
}
private void onFailure() {
int currentFailureCount = failureCount.incrementAndGet();
lastFailureTime.set(System.currentTimeMillis());
if (currentFailureCount >= failureThreshold) {
state = CircuitState.OPEN;
System.out.println("β Circuit breaker moving to OPEN state");
}
}
public CircuitState getState() {
return state;
}
@FunctionalInterface
public interface CircuitBreakerOperation<T> {
T execute() throws Exception;
}
}
enum CircuitState {
CLOSED, // Normal operation
OPEN, // Failing fast
HALF_OPEN // Testing if service recovered
}
class CircuitBreakerException extends Exception {
public CircuitBreakerException(String message) {
super(message);
}
public CircuitBreakerException(String message, Throwable cause) {
super(message, cause);
}
}
// Usage example
public class ExternalServiceClient {
private final CircuitBreaker circuitBreaker =
new CircuitBreaker(3, 10000, 2); // 3 failures, 10s timeout, 2 successes to close
public String callService(String request) throws CircuitBreakerException {
return circuitBreaker.execute(() -> {
// Simulate external service call
if (Math.random() < 0.6) {
throw new RuntimeException("Service failure");
}
return "Success: " + request;
});
}
}
}
4. π Exception Monitoring and Metrics β
Implement exception monitoring for production systems:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class ExceptionMonitoringExample {
public class ExceptionMetrics {
private final ConcurrentHashMap<String, AtomicLong> exceptionCounts = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, AtomicLong> exceptionTimestamps = new ConcurrentHashMap<>();
public void recordException(Exception exception) {
String exceptionType = exception.getClass().getSimpleName();
// Increment counter
exceptionCounts.computeIfAbsent(exceptionType, k -> new AtomicLong(0)).incrementAndGet();
// Record timestamp
exceptionTimestamps.put(exceptionType, new AtomicLong(System.currentTimeMillis()));
// Log with context
logException(exception);
// Send to monitoring system (e.g., Prometheus, CloudWatch)
sendToMonitoringSystem(exceptionType);
}
private void logException(Exception exception) {
System.err.printf("π¨ Exception recorded: %s - %s%n",
exception.getClass().getSimpleName(),
exception.getMessage());
}
private void sendToMonitoringSystem(String exceptionType) {
// Integration with monitoring systems
System.out.printf("π Metrics sent for exception type: %s%n", exceptionType);
}
public void printMetrics() {
System.out.println("π Exception Metrics:");
exceptionCounts.forEach((type, count) ->
System.out.printf("β’ %s: %d occurrences%n", type, count.get()));
}
public long getExceptionCount(String exceptionType) {
return exceptionCounts.getOrDefault(exceptionType, new AtomicLong(0)).get();
}
}
// Monitored service wrapper
public class MonitoredService {
private final ExceptionMetrics metrics = new ExceptionMetrics();
public String riskyOperation(String input) throws ServiceException {
try {
// Simulate risky operation
if (input == null) {
throw new ValidationException("Input cannot be null");
}
if (input.equals("fail")) {
throw new BusinessException("Business logic failure");
}
if (Math.random() < 0.3) {
throw new TransientException("Random transient failure");
}
return "Processed: " + input;
} catch (Exception e) {
metrics.recordException(e);
// Re-throw as ServiceException
if (e instanceof ServiceException) {
throw (ServiceException) e;
} else {
throw new ServiceException("Service operation failed", e);
}
}
}
public void printMetrics() {
metrics.printMetrics();
}
}
// Exception hierarchy for monitoring
abstract class ServiceException extends Exception {
public ServiceException(String message) { super(message); }
public ServiceException(String message, Throwable cause) { super(message, cause); }
}
class ValidationException extends ServiceException {
public ValidationException(String message) { super(message); }
}
class BusinessException extends ServiceException {
public BusinessException(String message) { super(message); }
}
class TransientException extends ServiceException {
public TransientException(String message) { super(message); }
}
}
π‘ Real-World Examples β
Here are comprehensive real-world examples demonstrating exception handling in various scenarios:
1. π¦ Banking System Example β
A complete banking system with proper exception handling:
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class BankingSystem {
// Custom exceptions for banking operations
public static class BankingException extends Exception {
private String errorCode;
private LocalDateTime timestamp;
public BankingException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
this.timestamp = LocalDateTime.now();
}
public String getErrorCode() { return errorCode; }
public LocalDateTime getTimestamp() { return timestamp; }
}
public static class InsufficientBalanceException extends BankingException {
private double availableBalance;
private double requestedAmount;
public InsufficientBalanceException(double availableBalance, double requestedAmount) {
super(String.format("π³ Insufficient balance. Available: $%.2f, Requested: $%.2f",
availableBalance, requestedAmount), "INSUFFICIENT_BALANCE");
this.availableBalance = availableBalance;
this.requestedAmount = requestedAmount;
}
public double getAvailableBalance() { return availableBalance; }
public double getRequestedAmount() { return requestedAmount; }
public double getDeficit() { return requestedAmount - availableBalance; }
}
public static class AccountNotFoundException extends BankingException {
public AccountNotFoundException(String accountNumber) {
super("π Account not found: " + accountNumber, "ACCOUNT_NOT_FOUND");
}
}
public static class InvalidTransactionException extends BankingException {
public InvalidTransactionException(String reason) {
super("β Invalid transaction: " + reason, "INVALID_TRANSACTION");
}
}
// Account class
public static class Account {
private final String accountNumber;
private double balance;
private final String accountHolderName;
private boolean isActive;
public Account(String accountNumber, String accountHolderName, double initialBalance) {
this.accountNumber = accountNumber;
this.accountHolderName = accountHolderName;
this.balance = initialBalance;
this.isActive = true;
}
public synchronized void withdraw(double amount) throws BankingException {
validateAccount();
validateAmount(amount);
if (amount > balance) {
throw new InsufficientBalanceException(balance, amount);
}
balance -= amount;
System.out.println(String.format("β
Withdrawal successful: $%.2f. New balance: $%.2f",
amount, balance));
}
public synchronized void deposit(double amount) throws BankingException {
validateAccount();
validateAmount(amount);
balance += amount;
System.out.println(String.format("β
Deposit successful: $%.2f. New balance: $%.2f",
amount, balance));
}
private void validateAccount() throws BankingException {
if (!isActive) {
throw new InvalidTransactionException("Account is inactive");
}
}
private void validateAmount(double amount) throws BankingException {
if (amount <= 0) {
throw new InvalidTransactionException(
String.format("Amount must be positive. Provided: $%.2f", amount));
}
if (amount > 10000) {
throw new InvalidTransactionException(
String.format("Amount exceeds daily limit of $10,000. Provided: $%.2f", amount));
}
}
// Getters
public String getAccountNumber() { return accountNumber; }
public double getBalance() { return balance; }
public String getAccountHolderName() { return accountHolderName; }
public boolean isActive() { return isActive; }
public void setActive(boolean active) { this.isActive = active; }
}
// Banking service
public static class BankingService {
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
public void createAccount(String accountNumber, String accountHolderName, double initialBalance)
throws BankingException {
if (accounts.containsKey(accountNumber)) {
throw new InvalidTransactionException("Account already exists: " + accountNumber);
}
if (initialBalance < 100) {
throw new InvalidTransactionException(
"Minimum initial balance is $100. Provided: $" + initialBalance);
}
Account account = new Account(accountNumber, accountHolderName, initialBalance);
accounts.put(accountNumber, account);
System.out.println(String.format("β
Account created successfully: %s for %s with balance $%.2f",
accountNumber, accountHolderName, initialBalance));
}
public void performTransaction(String accountNumber, String operation, double amount)
throws BankingException {
Account account = accounts.get(accountNumber);
if (account == null) {
throw new AccountNotFoundException(accountNumber);
}
try {
switch (operation.toLowerCase()) {
case "withdraw":
account.withdraw(amount);
break;
case "deposit":
account.deposit(amount);
break;
default:
throw new InvalidTransactionException("Unknown operation: " + operation);
}
} catch (BankingException e) {
// Log the transaction failure
System.err.printf("π
Transaction failed at %s: %s%n",
e.getTimestamp(), e.getMessage());
throw e; // Re-throw for caller to handle
}
}
public void transferFunds(String fromAccount, String toAccount, double amount)
throws BankingException {
// This is a compound operation - needs transaction-like behavior
Account from = accounts.get(fromAccount);
Account to = accounts.get(toAccount);
if (from == null) {
throw new AccountNotFoundException(fromAccount);
}
if (to == null) {
throw new AccountNotFoundException(toAccount);
}
try {
// Withdraw from source account
from.withdraw(amount);
try {
// Deposit to destination account
to.deposit(amount);
System.out.println(String.format("β
Transfer completed: $%.2f from %s to %s",
} catch (BankingException e) {
// Rollback: deposit back to source account
try {
from.deposit(amount);
System.err.println("π Transfer rolled back due to deposit failure");
} catch (BankingException rollbackException) {
System.err.println("β Critical error: Rollback failed! Manual intervention required.");
// In real system, this would trigger alerts
}
throw new InvalidTransactionException("Transfer failed: " + e.getMessage());
}
} catch (BankingException e) {
throw new InvalidTransactionException("Transfer failed: " + e.getMessage());
}
}
public Account getAccount(String accountNumber) throws AccountNotFoundException {
Account account = accounts.get(accountNumber);
if (account == null) {
throw new AccountNotFoundException(accountNumber);
}
return account;
}
}
// Demo application
public static void main(String[] args) {
BankingService bankingService = new BankingService();
try {
System.out.println("π¦ Banking System Demo\n");
// Create accounts
bankingService.createAccount("ACC-001", "John Doe", 1000.0);
bankingService.createAccount("ACC-002", "Jane Smith", 500.0);
System.out.println();
// Perform various transactions
System.out.println("π° Performing transactions:");
bankingService.performTransaction("ACC-001", "deposit", 200.0);
bankingService.performTransaction("ACC-001", "withdraw", 150.0);
System.out.println();
// Transfer funds
System.out.println("π Transferring funds:");
bankingService.transferFunds("ACC-001", "ACC-002", 300.0);
System.out.println();
// Demonstrate exception handling
System.out.println("β οΈ Testing exception scenarios:");
try {
bankingService.performTransaction("ACC-001", "withdraw", 2000.0);
} catch (InsufficientBalanceException e) {
System.err.println("π¨ Caught insufficient balance: " + e.getMessage());
System.err.println("π Deficit: $" + e.getDeficit());
}
try {
bankingService.performTransaction("ACC-999", "deposit", 100.0);
} catch (AccountNotFoundException e) {
System.err.println("π¨ Caught account not found: " + e.getMessage());
}
} catch (BankingException e) {
System.err.println("π¨ Banking operation failed: " + e.getMessage());
System.err.println("π Error code: " + e.getErrorCode());
System.err.println("π
Timestamp: " + e.getTimestamp());
}
}
}
2. π Web Service Client with Retry Logic β
A robust web service client with comprehensive error handling:
import java.io.*;
import java.net.*;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicInteger;
public class WebServiceClient {
// Custom exceptions
public static class ServiceException extends Exception {
private final int statusCode;
private final String endpoint;
public ServiceException(String message, String endpoint) {
super(message);
this.statusCode = -1;
this.endpoint = endpoint;
}
public ServiceException(String message, String endpoint, int statusCode) {
super(message);
this.statusCode = statusCode;
this.endpoint = endpoint;
}
public ServiceException(String message, String endpoint, Throwable cause) {
super(message, cause);
this.statusCode = -1;
this.endpoint = endpoint;
}
public int getStatusCode() { return statusCode; }
public String getEndpoint() { return endpoint; }
}
public static class TransientServiceException extends ServiceException {
public TransientServiceException(String message, String endpoint) {
super(message, endpoint);
}
public TransientServiceException(String message, String endpoint, int statusCode) {
super(message, endpoint, statusCode);
}
}
public static class PermanentServiceException extends ServiceException {
public PermanentServiceException(String message, String endpoint, int statusCode) {
super(message, endpoint, statusCode);
}
}
// Service response class
public static class ServiceResponse {
private final int statusCode;
private final String body;
private final boolean success;
public ServiceResponse(int statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
this.success = statusCode >= 200 && statusCode < 300;
}
public int getStatusCode() { return statusCode; }
public String getBody() { return body; }
public boolean isSuccess() { return success; }
}
// HTTP client with retry logic
public static class ResilientHttpClient {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
private static final long BACKOFF_MULTIPLIER = 2;
private final AtomicInteger requestCounter = new AtomicInteger(0);
public ServiceResponse callService(String endpoint, String requestBody) throws ServiceException {
int requestId = requestCounter.incrementAndGet();
System.out.printf("π [Request #%d] Calling service: %s%n", requestId, endpoint);
return executeWithRetry(endpoint, requestBody, requestId);
}
private ServiceResponse executeWithRetry(String endpoint, String requestBody, int requestId)
throws ServiceException {
int attempts = 0;
long currentDelay = RETRY_DELAY_MS;
Exception lastException = null;
while (attempts < MAX_RETRIES) {
attempts++;
try {
System.out.printf("π [Request #%d] Attempt %d/%d%n", requestId, attempts, MAX_RETRIES);
ServiceResponse response = performHttpCall(endpoint, requestBody);
if (response.isSuccess()) {
System.out.printf("β
[Request #%d] Success on attempt %d%n", requestId, attempts);
return response;
} else {
handleErrorResponse(response, endpoint, attempts, requestId);
}
} catch (IOException e) {
lastException = e;
System.err.printf("β οΈ [Request #%d] Network error on attempt %d: %s%n",
requestId, attempts, e.getMessage());
if (attempts < MAX_RETRIES) {
waitBeforeRetry(currentDelay, requestId, attempts);
currentDelay *= BACKOFF_MULTIPLIER;
}
} catch (TransientServiceException e) {
lastException = e;
System.err.printf("β οΈ [Request #%d] Transient error on attempt %d: %s%n",
requestId, attempts, e.getMessage());
if (attempts < MAX_RETRIES) {
waitBeforeRetry(currentDelay, requestId, attempts);
currentDelay *= BACKOFF_MULTIPLIER;
}
} catch (PermanentServiceException e) {
System.err.printf("β [Request #%d] Permanent error, not retrying: %s%n",
requestId, e.getMessage());
throw e;
}
}
// All retries exhausted
System.err.printf("π₯ [Request #%d] All %d attempts failed%n", requestId, MAX_RETRIES);
throw new ServiceException(
String.format("Service call failed after %d attempts", MAX_RETRIES),
endpoint,
lastException);
}
private ServiceResponse performHttpCall(String endpoint, String requestBody) throws IOException, ServiceException {
// Simulate HTTP call
System.out.printf("π Making HTTP call to: %s%n", endpoint);
// Simulate network delay
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServiceException("Request interrupted", endpoint, e);
}
// Simulate different response scenarios
double random = Math.random();
if (random < 0.3) {
// Simulate network error
throw new IOException("Connection timeout");
} else if (random < 0.6) {
// Simulate server error (transient)
int statusCode = 503; // Service Unavailable
throw new TransientServiceException("Service temporarily unavailable", endpoint, statusCode);
} else if (random < 0.8) {
// Simulate client error (permanent)
int statusCode = 400; // Bad Request
throw new PermanentServiceException("Bad request format", endpoint, statusCode);
} else {
// Success
return new ServiceResponse(200, "{\"result\": \"success\", \"data\": \"response data\"}");
}
}
private void handleErrorResponse(ServiceResponse response, String endpoint,
int attempt, int requestId) throws ServiceException {
int statusCode = response.getStatusCode();
if (statusCode >= 500 && statusCode < 600) {
// Server errors are typically transient
throw new TransientServiceException(
String.format("Server error: %d", statusCode), endpoint, statusCode);
} else if (statusCode == 429) {
// Rate limiting - transient
throw new TransientServiceException(
"Rate limit exceeded", endpoint, statusCode);
} else if (statusCode >= 400 && statusCode < 500) {
// Client errors are typically permanent
throw new PermanentServiceException(
String.format("Client error: %d", statusCode), endpoint, statusCode);
} else {
// Unexpected status code
throw new ServiceException(
String.format("Unexpected response: %d", statusCode), endpoint, statusCode);
}
}
private void waitBeforeRetry(long delay, int requestId, int attempt) {
try {
System.out.printf("π΄ [Request #%d] Waiting %dms before retry (attempt %d)%n",
requestId, delay, attempt + 1);
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.printf("β [Request #%d] Retry interrupted%n", requestId);
}
}
}
// Demo application
public static void main(String[] args) {
ResilientHttpClient client = new ResilientHttpClient();
System.out.println("π Web Service Client Demo\n");
// Test multiple service calls
String[] endpoints = {
"/api/users",
"/api/orders",
"/api/products"
};
for (String endpoint : endpoints) {
try {
System.out.println("π Testing endpoint: " + endpoint);
ServiceResponse response = client.callService("https://api.example.com" + endpoint,
"{\"request\": \"data\"}");
System.out.printf("β
Response: Status=%d, Body=%s%n%n",
response.getStatusCode(), response.getBody());
} catch (PermanentServiceException e) {
System.err.printf("β Permanent failure for %s: %s (Status: %d)%n%n",
e.getEndpoint(), e.getMessage(), e.getStatusCode());
} catch (ServiceException e) {
System.err.printf("β Service call failed for %s: %s%n", e.getEndpoint(), e.getMessage());
if (e.getCause() != null) {
System.err.printf("π Caused by: %s%n", e.getCause().getMessage());
}
System.out.println();
}
}
}
}
3. π File Processing System β
A robust file processing system with comprehensive error handling:
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
public class FileProcessingSystem {
// Custom exceptions for file operations
public static class FileProcessingException extends Exception {
private final String fileName;
private final String operation;
public FileProcessingException(String message, String fileName, String operation) {
super(message);
this.fileName = fileName;
this.operation = operation;
}
public FileProcessingException(String message, String fileName, String operation, Throwable cause) {
super(message, cause);
this.fileName = fileName;
this.operation = operation;
}
public String getFileName() { return fileName; }
public String getOperation() { return operation; }
}
public static class InvalidFileFormatException extends FileProcessingException {
public InvalidFileFormatException(String message, String fileName) {
super(message, fileName, "VALIDATION");
}
}
public static class FileAccessException extends FileProcessingException {
public FileAccessException(String message, String fileName, String operation, Throwable cause) {
super(message, fileName, operation, cause);
}
}
// Processing result class
public static class ProcessingResult {
private final String fileName;
private final boolean success;
private final int recordsProcessed;
private final List<String> errors;
private final LocalDateTime processedAt;
public ProcessingResult(String fileName, boolean success, int recordsProcessed, List<String> errors) {
this.fileName = fileName;
this.success = success;
this.recordsProcessed = recordsProcessed;
this.errors = new ArrayList<>(errors);
this.processedAt = LocalDateTime.now();
}
// Getters
public String getFileName() { return fileName; }
public boolean isSuccess() { return success; }
public int getRecordsProcessed() { return recordsProcessed; }
public List<String> getErrors() { return new ArrayList<>(errors); }
public LocalDateTime getProcessedAt() { return processedAt; }
}
// File processor with comprehensive error handling
public static class RobustFileProcessor {
private final Queue<String> processingLog = new ConcurrentLinkedQueue<>();
private final String outputDirectory;
public RobustFileProcessor(String outputDirectory) {
this.outputDirectory = outputDirectory;
createOutputDirectory();
}
private void createOutputDirectory() {
try {
Path outputPath = Paths.get(outputDirectory);
if (!Files.exists(outputPath)) {
Files.createDirectories(outputPath);
log("π Created output directory: " + outputDirectory);
}
} catch (IOException e) {
log("β Failed to create output directory: " + e.getMessage());
}
}
public ProcessingResult processFile(String inputFilePath) {
String fileName = Paths.get(inputFilePath).getFileName().toString();
List<String> errors = new ArrayList<>();
int recordsProcessed = 0;
log(String.format("π Starting processing: %s", fileName));
try {
// Validate file exists and is readable
validateFile(inputFilePath);
// Process the file with resource management
recordsProcessed = processFileContent(inputFilePath, errors);
log(String.format("β
Successfully processed %d records from %s", recordsProcessed, fileName));
return new ProcessingResult(fileName, true, recordsProcessed, errors);
} catch (FileProcessingException e) {
String errorMsg = String.format("β Processing failed for %s: %s", fileName, e.getMessage());
log(errorMsg);
errors.add(errorMsg);
return new ProcessingResult(fileName, false, recordsProcessed, errors);
} catch (Exception e) {
String errorMsg = String.format("β Unexpected error processing %s: %s", fileName, e.getMessage());
log(errorMsg);
errors.add(errorMsg);
return new ProcessingResult(fileName, false, recordsProcessed, errors);
}
}
private void validateFile(String filePath) throws FileProcessingException {
Path path = Paths.get(filePath);
if (!Files.exists(path)) {
throw new FileAccessException(
"File does not exist", filePath, "READ",
new FileNotFoundException(filePath));
}
if (!Files.isReadable(path)) {
throw new FileAccessException(
"File is not readable", filePath, "READ",
new AccessDeniedException(filePath));
}
if (!Files.isRegularFile(path)) {
throw new FileAccessException(
"Path is not a regular file", filePath, "READ", null);
}
// Check file extension
String fileName = path.getFileName().toString().toLowerCase();
if (!fileName.endsWith(".txt") && !fileName.endsWith(".csv")) {
throw new InvalidFileFormatException(
"Unsupported file format. Only .txt and .csv files are supported", filePath);
}
}
private int processFileContent(String inputFilePath, List<String> errors)
throws FileProcessingException {
String fileName = Paths.get(inputFilePath).getFileName().toString();
String outputFilePath = Paths.get(outputDirectory, "processed_" + fileName).toString();
int recordsProcessed = 0;
int lineNumber = 0;
// Use try-with-resources for automatic resource management
try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputFilePath));
BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputFilePath))) {
String line;
while ((line = reader.readLine()) != null) {
lineNumber++;
try {
// Process each line
String processedLine = processLine(line, lineNumber);
writer.write(processedLine);
writer.newLine();
recordsProcessed++;
} catch (InvalidFileFormatException e) {
String errorMsg = String.format("Line %d: %s", lineNumber, e.getMessage());
errors.add(errorMsg);
log("β οΈ " + errorMsg);
// Continue processing other lines
}
}
writer.flush();
log(String.format("π Output written to: %s", outputFilePath));
} catch (IOException e) {
throw new FileAccessException(
"IO error during file processing", inputFilePath, "READ_WRITE", e);
}
return recordsProcessed;
}
private String processLine(String line, int lineNumber) throws InvalidFileFormatException {
if (line == null || line.trim().isEmpty()) {
throw new InvalidFileFormatException("Empty or null line", String.valueOf(lineNumber));
}
// Simulate data validation and processing
String[] parts = line.split(",");
if (parts.length < 2) {
throw new InvalidFileFormatException("Line must have at least 2 comma-separated values", String.valueOf(lineNumber));
}
// Simulate data transformation
StringBuilder processedLine = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
if (i > 0) processedLine.append(",");
processedLine.append(parts[i].trim().toUpperCase());
}
// Add timestamp
processedLine.append(",PROCESSED_AT=")
.append(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return processedLine.toString();
}
private void log(String message) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
String logEntry = String.format("[%s] %s", timestamp, message);
processingLog.offer(logEntry);
System.out.println(logEntry);
}
public void printProcessingLog() {
System.out.println("\nπ Processing Log:");
processingLog.forEach(System.out::println);
}
public void saveProcessingLog(String logFilePath) {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(logFilePath))) {
for (String logEntry : processingLog) {
writer.write(logEntry);
writer.newLine();
}
log("πΎ Processing log saved to: " + logFilePath);
} catch (IOException e) {
log("β Failed to save processing log: " + e.getMessage());
}
}
}
// Demo application
public static void main(String[] args) {
System.out.println("π File Processing System Demo\n");
RobustFileProcessor processor = new RobustFileProcessor("output");
// Create sample test files
createTestFiles();
// Test files to process
String[] testFiles = {
"test_data.txt",
"invalid_data.csv",
"nonexistent.txt",
"empty_file.txt"
};
List<ProcessingResult> results = new ArrayList<>();
for (String testFile : testFiles) {
System.out.println("ββββββββββββββββββββββββββββββ");
ProcessingResult result = processor.processFile(testFile);
results.add(result);
System.out.println();
}
// Print summary
printProcessingSummary(results);
// Save processing log
processor.saveProcessingLog("processing_log.txt");
}
private static void createTestFiles() {
try {
// Create valid test file
Files.write(Paths.get("test_data.txt"), Arrays.asList(
"John,Doe,Engineer",
"Jane,Smith,Manager",
"Bob,Johnson,Developer"
));
// Create file with invalid data
Files.write(Paths.get("invalid_data.csv"), Arrays.asList(
"Valid,Data,Entry",
"Invalid", // Missing fields
"Another,Valid,Entry",
"", // Empty line
"Final,Valid,Entry"
));
// Create empty file
Files.write(Paths.get("empty_file.txt"), Collections.emptyList());
} catch (IOException e) {
System.err.println("β Failed to create test files: " + e.getMessage());
}
}
private static void printProcessingSummary(List<ProcessingResult> results) {
System.out.println("π Processing Summary:");
System.out.println("=".repeat(60));
int totalFiles = results.size();
int successfulFiles = 0;
int totalRecordsProcessed = 0;
for (ProcessingResult result : results) {
if (result.isSuccess()) {
successfulFiles++;
totalRecordsProcessed += result.getRecordsProcessed();
}
String status = result.isSuccess() ? "β
SUCCESS" : "β FAILED";
System.out.printf("%-20s %s (%d records)%n",
result.getFileName(), status, result.getRecordsProcessed());
if (!result.getErrors().isEmpty()) {
result.getErrors().forEach(error -> System.out.println(" β οΈ " + error));
}
}
System.out.println("=".repeat(60));
System.out.printf("π Files processed: %d/%d successful%n", successfulFiles, totalFiles);
System.out.printf("π Total records processed: %d%n", totalRecordsProcessed);
}
}
π Summary β
π Key Takeaways β
Exception handling in Java is a powerful mechanism that enables developers to write robust, maintainable applications. Here are the essential concepts to remember:
ποΈ Core Concepts β
Concept | Description | Best Practice |
---|---|---|
π Checked Exceptions | Compile-time verified exceptions | Use for recoverable conditions |
β‘ Unchecked Exceptions | Runtime exceptions | Use for programming errors |
π₯ Errors | System-level problems | Don't catch, let them propagate |
π― try-catch | Exception handling blocks | Be specific with catch blocks |
π§Ή finally | Always executed block | Use for cleanup operations |
π throw | Explicitly throw exceptions | Validate inputs and throw meaningful exceptions |
π’ throws | Declare potential exceptions | Document all possible exceptions |
π¨ Best Practices Checklist β
- β
Be Specific: Use specific exception types, not generic
Exception
- β Meaningful Messages: Provide clear, actionable error messages
- β Resource Management: Always clean up resources (use try-with-resources)
- β Don't Swallow: Never catch exceptions silently
- β Log Appropriately: Log errors with context before re-throwing
- β Fail Fast: Validate inputs early and fail fast
- β Exception Translation: Convert low-level exceptions to business exceptions
- β Document Exceptions: Clearly document what exceptions methods can throw
π Exception Hierarchy Guidelines β
β‘ Advanced Patterns β
- π Retry Pattern: For transient failures
- π Circuit Breaker: Prevent cascade failures
- π Exception Translation: Layer-appropriate exceptions
- π Monitoring: Track exceptions for insights
- π Structured Logging: Log with correlation IDs
π When to Use Each Exception Type β
π Exception Handling Maturity Model β
Level | Description | Characteristics |
---|---|---|
π₯ Basic | Minimal exception handling | try-catch with printStackTrace() |
π¨ Functional | Proper exception types | Specific exceptions, meaningful messages |
π¦ Structured | Layered exception handling | Exception translation, proper cleanup |
π© Advanced | Resilient systems | Retry logic, circuit breakers, monitoring |
π§ Expert | Proactive exception design | Exception hierarchies, business-driven exceptions |
π§ Custom Exceptions β
Creating your own exception classes for specific business logic:
Why Create Custom Exceptions? β
- π― Specific Error Handling: Handle domain-specific errors
- π Better Error Messages: Meaningful error descriptions
- π Easy Debugging: Clear error identification
- ποΈ Clean Architecture: Separate business logic errors
Types of Custom Exceptions: β
Custom Checked Exception: β
// Custom checked exception for banking operations
public class InsufficientFundsException extends Exception {
private double currentBalance;
private double requestedAmount;
// Constructor with message only
public InsufficientFundsException(String message) {
super(message);
}
// Constructor with message and cause
public InsufficientFundsException(String message, Throwable cause) {
super(message, cause);
}
// Constructor with detailed information
public InsufficientFundsException(String message, double currentBalance, double requestedAmount) {
super(message);
this.currentBalance = currentBalance;
this.requestedAmount = requestedAmount;
}
// Getter methods
public double getCurrentBalance() {
return currentBalance;
}
public double getRequestedAmount() {
return requestedAmount;
}
public double getDeficit() {
return requestedAmount - currentBalance;
}
@Override
public String toString() {
return String.format("InsufficientFundsException: %s [Balance: $%.2f, Requested: $%.2f, Deficit: $%.2f]",
getMessage(), currentBalance, requestedAmount, getDeficit());
}
}
// Bank Account class using custom exception
class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("π° Withdrawal amount must be positive");
}
if (amount > balance) {
throw new InsufficientFundsException(
"π³ Insufficient funds for withdrawal",
balance, amount);
}
balance -= amount;
System.out.println(String.format("β
Withdrawn $%.2f. New balance: $%.2f", amount, balance));
}
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
}
Custom Unchecked Exception: β
// Custom unchecked exception for validation errors
public class ValidationException extends RuntimeException {
private String fieldName;
private Object fieldValue;
private String validationRule;
public ValidationException(String message) {
super(message);
}
public ValidationException(String message, String fieldName, Object fieldValue) {
super(message);
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
public ValidationException(String message, String fieldName, Object fieldValue, String validationRule) {
super(message);
this.fieldName = fieldName;
this.fieldValue = fieldValue;
this.validationRule = validationRule;
}
public String getFieldName() {
return fieldName;
}
public Object getFieldValue() {
return fieldValue;
}
public String getValidationRule() {
return validationRule;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ValidationException: ");
sb.append(getMessage());
if (fieldName != null) {
sb.append(" [Field: ").append(fieldName);
if (fieldValue != null) {
sb.append(", Value: ").append(fieldValue);
}
if (validationRule != null) {
sb.append(", Rule: ").append(validationRule);
}
sb.append("]");
}
return sb.toString();
}
}
// User class with validation
class User {
private String username;
private String email;
private int age;
public User(String username, String email, int age) {
setUsername(username);
setEmail(email);
setAge(age);
}
public void setUsername(String username) {
if (username == null || username.trim().isEmpty()) {
throw new ValidationException(
"π€ Username cannot be empty",
"username", username, "NOT_EMPTY");
}
if (username.length() < 3) {
throw new ValidationException(
"π€ Username must be at least 3 characters",
"username", username, "MIN_LENGTH_3");
}
this.username = username;
}
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new ValidationException(
"π§ Invalid email format",
"email", email, "VALID_EMAIL");
}
this.email = email;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new ValidationException(
"π Age must be between 0 and 150",
"age", age, "VALID_RANGE");
}
this.age = age;
}
// Getters
public String getUsername() { return username; }
public String getEmail() { return email; }
public int getAge() { return age; }
}
Exception Hierarchy for Custom Exceptions: β
// Base business exception
public abstract class BusinessException extends Exception {
private String errorCode;
private long timestamp;
public BusinessException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
this.timestamp = System.currentTimeMillis();
}
public String getErrorCode() { return errorCode; }
public long getTimestamp() { return timestamp; }
}
// Specific business exceptions
class OrderException extends BusinessException {
public OrderException(String message, String errorCode) {
super(message, errorCode);
}
}
class PaymentException extends BusinessException {
private String transactionId;
public PaymentException(String message, String errorCode, String transactionId) {
super(message, errorCode);
this.transactionId = transactionId;
}
public String getTransactionId() { return transactionId; }
}
class InventoryException extends BusinessException {
private String productId;
private int availableQuantity;
private int requestedQuantity;
public InventoryException(String message, String errorCode,
String productId, int availableQuantity, int requestedQuantity) {
super(message, errorCode);
this.productId = productId;
this.availableQuantity = availableQuantity;
this.requestedQuantity = requestedQuantity;
}
// Getters
public String getProductId() { return productId; }
public int getAvailableQuantity() { return availableQuantity; }
public int getRequestedQuantity() { return requestedQuantity; }
}
Using Custom Exceptions: β
public class CustomExceptionDemo {
public static void demonstrateBankingException() {
BankAccount account = new BankAccount("ACC-001", 500.0);
try {
account.withdraw(750.0); // This will throw InsufficientFundsException
} catch (InsufficientFundsException e) {
System.err.println("π¦ Banking Error: " + e.getMessage());
System.err.println("π° Current Balance: $" + e.getCurrentBalance());
System.err.println("πΈ Requested Amount: $" + e.getRequestedAmount());
System.err.println("π Deficit: $" + e.getDeficit());
}
}
public static void demonstrateValidationException() {
try {
User user = new User("", "invalid-email", -5); // Multiple validation errors
} catch (ValidationException e) {
System.err.println("β Validation Error: " + e.getMessage());
System.err.println("π Field: " + e.getFieldName());
System.err.println("π‘ Rule: " + e.getValidationRule());
System.err.println("π Full details: " + e.toString());
}
}
public static void main(String[] args) {
System.out.println("π― Demonstrating Custom Exceptions:\n");
System.out.println("1οΈβ£ Banking Exception Demo:");
demonstrateBankingException();
System.out.println("\n2οΈβ£ Validation Exception Demo:");
demonstrateValidationException();
}
}
π Multiple Catch Blocks β
You can handle different types of exceptions with multiple catch blocks:
Rules for Multiple Catch Blocks: β
- π― Specific to General: More specific exceptions first
- π One Match Only: Only one catch block executes
- π Order Matters: Subclass exceptions before superclass
- π« Unreachable Code: Avoid unreachable catch blocks
Example: β
import java.io.*;
import java.sql.*;
public class MultipleCatchExample {
public static void demonstrateMultipleCatch() {
try {
// Code that can throw different exceptions
String input = getUserInput();
int number = Integer.parseInt(input); // NumberFormatException
int result = 100 / number; // ArithmeticException
FileReader file = new FileReader("data.txt"); // FileNotFoundException
int[] array = new int[5];
array[number] = result; // ArrayIndexOutOfBoundsException
} catch (NumberFormatException e) {
System.err.println("π’ Invalid number format: " + e.getMessage());
} catch (ArithmeticException e) {
System.err.println("β Math error (division by zero): " + e.getMessage());
} catch (FileNotFoundException e) {
System.err.println("π File not found: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("π Array index error: " + e.getMessage());
} catch (IOException e) {
// This catches other IO exceptions
System.err.println("πΎ IO Error: " + e.getMessage());
} catch (RuntimeException e) {
// This catches other runtime exceptions
System.err.println("β‘ Runtime error: " + e.getMessage());
} catch (Exception e) {
// This catches any other exception
System.err.println("β General exception: " + e.getMessage());
} finally {
System.out.println("π§Ή Cleanup completed");
}
}
private static String getUserInput() {
// Simulated user input
return "invalid_number";
}
}
Multi-Catch Block (Java 7+): β
public class MultiCatchExample {
public static void demonstrateMultiCatch() {
try {
// Code that might throw different exceptions
performRiskyOperation();
} catch (IOException | SQLException | ClassNotFoundException e) {
// Handle multiple exception types with same logic
System.err.println("β Multiple exception types: " + e.getClass().getSimpleName());
System.err.println("π Message: " + e.getMessage());
logError(e);
} catch (RuntimeException e) {
System.err.println("β‘ Runtime exception: " + e.getMessage());
}
}
private static void logError(Exception e) {
// Common error logging logic
System.out.println("π Logged error: " + e.getClass().getSimpleName());
}
private static void performRiskyOperation() throws IOException, SQLException, ClassNotFoundException {
// Implementation here
}
}
// Remember that in a single catch block, you can write multiple exception types but but there should not be parent child or child parent relationship between any two exception.
Wrong vs Right Order: β
public class CatchOrderExample {
// β WRONG - This won't compile
public static void wrongOrder() {
try {
throw new FileNotFoundException("File not found");
} catch (Exception e) {
// This catches all exceptions
System.out.println("General exception");
} catch (IOException e) {
// β UNREACHABLE - IOException is subclass of Exception
System.out.println("IO exception");
}
}
// β
CORRECT - Specific to general order
public static void correctOrder() {
try {
throw new FileNotFoundException("File not found");
} catch (FileNotFoundException e) {
System.out.println("π File not found exception");
} catch (IOException e) {
System.out.println("πΎ IO exception");
} catch (Exception e) {
System.out.println("β General exception");
}
}
}
π Learning Path β
- π Master the Basics: Understand exception hierarchy and keywords
- π― Practice with Examples: Work through real-world scenarios
- π§ Build Custom Exceptions: Create meaningful business exceptions
- π¨ Apply Best Practices: Follow established patterns and guidelines
- β‘ Learn Advanced Patterns: Implement retry logic and circuit breakers
- π Add Monitoring: Track and analyze exception patterns
π Additional Resources β
For further learning, explore:
- Oracle Java Documentation: Official exception handling guide
- Effective Java by Joshua Bloch: Exception handling best practices
- Java Concurrency in Practice: Exception handling in concurrent code
- Microservices Patterns: Resilience patterns for distributed systems