Exception Handling
1. Introduction
Exception handling in Java is a powerful mechanism that handles runtime errors, maintains the normal flow of the application, and prevents the program from crashing unexpectedly.
2. What is an Exception?
An exception is an unwanted or unexpected event that disrupts the normal flow of a program. It is an object which is thrown at runtime.
Example: Dividing a number by zero results in an ArithmeticException.
int result = 10 / 0; // Throws ArithmeticException
3. Why Exception Handling is Needed?
- To prevent abnormal termination.
- To separate error-handling code from regular code.
- To gracefully handle errors.
- To maintain the application flow.
4. Types of Exceptions in Java
In Java, exceptions are events that disrupt the normal flow of a program’s execution. They are objects that represent an error or an unexpected behavior during program execution.
Java categorizes exceptions into three main types:
a. Checked Exceptions
Definition:
Checked exceptions are those that are checked at compile-time.
The compiler ensures that the programmer handles these exceptions either using a try-catch block or by declaring them using the throws keyword.
If not handled properly, the program will not compile.
When to Use:
- When the program needs to recover from the exception or inform the user gracefully.
- Typically used for external events or resources, like file handling, network access, or database connections.
Common Examples:
- IOException
- SQLException
- FileNotFoundException
- ClassNotFoundException
Example:
import java.io.*;
public class CheckedExample {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("file.txt"); // may throw FileNotFoundException
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
}
}
}
Explanation:
- The compiler forces you to handle FileNotFoundException because it’s a checked exception.
- This ensures safer code that anticipates potential problems.
b. Unchecked Exceptions
Definition:
Unchecked exceptions are not checked at compile-time.
These exceptions occur due to programming errors such as logic mistakes or incorrect API usage.
The compiler does not force you to handle them, but it's good practice to anticipate and handle them where appropriate.
When to Use:
- Typically represent bugs in the code.
- Can be avoided through better coding practices and input validation.
Common Examples:
- ArithmeticException
- NullPointerException
- ArrayIndexOutOfBoundsException
- IllegalArgumentException
Example:
public class UncheckedExample {
public static void main(String[] args) {
int a = 10;
int b = 0;
int result = a / b; // throws ArithmeticException
System.out.println("Result: " + result);
}
}
Explanation:
- Dividing by zero throws an ArithmeticException, which is unchecked.
- Program will compile, but fail at runtime unless handled properly.
c. Errors
Definition:
Errors are serious issues that occur in the JVM (Java Virtual Machine) and are not meant to be handled by the application code.
They typically indicate system-level problems that are beyond the control of the application, such as memory overflows or JVM crashes.
When to Use:
- You should not attempt to catch or handle Errors in most cases.
- Let the JVM handle them.
Common Examples:
- OutOfMemoryError
- StackOverflowError
- VirtualMachineError
Example:
public class ErrorExample {
public static void recursive() {
recursive(); // causes StackOverflowError
}
public static void main(String[] args) {
recursive();
}
}
Explanation:
- The recursive call goes on infinitely, eventually exhausting the call stack and causing a StackOverflowError.
- This is not something you typically catch with a try-catch block.
Summary Table
Type | Checked at | Recoverable? | Common Examples |
---|---|---|---|
Checked Exception | Compile-Time | Yes | IOException, SQLException |
Unchecked Exception | Runtime | Often Yes | NullPointerException, ArithmeticException |
Error | Runtime | No | OutOfMemoryError, StackOverflowError |
5. Java Exception Hierarchy
In Java, all exceptions and errors are part of a class hierarchy rooted in the class java.lang.Object.
The topmost class in this hierarchy for handling errors and exceptions is Throwable, which has two direct subclasses:

- Exception
- Error
Hierarchy Structure:
java.lang.Object
└── java.lang.Throwable
├── java.lang.Error (unchecked)
└── java.lang.Exception
├── Checked Exceptions
└── java.lang.RuntimeException (unchecked)
1. Throwable (Superclass for All Errors and Exceptions)
- Throwable is the superclass for all errors and exceptions in Java.
- It provides common methods like getMessage(), printStackTrace(), and toString().
- Only objects that are instances of Throwable or its subclasses can be thrown using the throw statement or caught using try-catch.
2. Error (Unchecked)
- Subclass of Throwable.
- Represents serious issues that are not intended to be caught by applications.
- Mostly caused by the JVM or system-level problems.
- Not recoverable through program logic.
Common Subclasses of Error:
- OutOfMemoryError
- StackOverflowError
- VirtualMachineError
- AssertionError
Example:
public class ErrorExample {
public static void main(String[] args) {
recursive(); // StackOverflowError
}
public static void recursive() {
recursive();
}
}
3. Exception (Can Be Checked or Unchecked)
- Also a subclass of Throwable.
- Represents conditions that a program might want to catch and handle.
- Divided into:
- Checked Exceptions
- Unchecked Exceptions (RuntimeException and its subclasses)
3.1 Checked Exceptions
- Checked at compile-time.
- Must be handled using try-catch or declared using throws.
Common Checked Exceptions:
- IOException
- SQLException
- FileNotFoundException
- ClassNotFoundException
Example:
import java.io.*;
public class CheckedExample {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("file.txt");
} catch (FileNotFoundException e) {
System.out.println("File not found!");
}
}
}
3.2 Unchecked Exceptions (RuntimeException and its subclasses)
- Not checked at compile-time.
- Caused by programming logic errors and can occur during program execution.
- Program compiles fine but may crash at runtime if not handled.
Common Unchecked Exceptions:
- NullPointerException
- ArithmeticException
- ArrayIndexOutOfBoundsException
- IllegalArgumentException
Example:
public class UncheckedExample {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // NullPointerException
}
}
Summary Table:
Class | Type | Checked at | Recoverable? | Examples |
---|---|---|---|---|
Throwable | Base class | — | — | — |
Error | Unchecked | Runtime | No | OutOfMemoryError, StackOverflowError |
Exception | General exception | Compile/Run | Yes | — |
→ Checked Exception | Checked Exception | Compile-Time | Yes | IOException, SQLException |
→ RuntimeException | Unchecked | Runtime | Often Yes | NullPointerException, ArithmeticException |
Visual Representation:
Object
└── Throwable
├── Error (not meant to be caught)
│ └── OutOfMemoryError, StackOverflowError
└── Exception
├── Checked Exceptions (must be handled)
│ └── IOException, SQLException
└── RuntimeException (unchecked)
└── NullPointerException, ArithmeticException
6. Difference Between Errors and Exceptions
Feature | Exception | Error |
---|---|---|
Recoverable | Yes | No |
Handled by program | Yes | No |
Example | FileNotFoundException | OutOfMemoryError |
7. Basic Syntax: try, catch, finally
Exception handling in Java is done using the following keywords:
- try
- catch
- finally
- throw
- throws
In this section, we’ll focus on the basic structure of try-catch-finally blocks.
Syntax:
try {
// code that may throw an exception
} catch (ExceptionType name) {
// code to handle the exception
} finally {
// code that will always execute
}
Explanation of Each Block:
try block
- The try block contains code that might throw an exception.
- If an exception occurs, control is transferred to the corresponding catch block.
- If no exception occurs, the catch block is skipped.
try {
int result = 10 / 0; // risky code
}
catch block
- The catch block handles the exception.
- You can have multiple catch blocks to handle different types of exceptions separately.
- If the exception matches the type declared in the catch block, that block will execute.
catch (ArithmeticException e) {
System.out.println("Cannot divide by zero.");
}
finally block
- The finally block contains code that always executes, whether an exception is thrown or not.
- Typically used for resource cleanup, like closing files, database connections, or network sockets.
finally {
System.out.println("Finally block is always executed.");
}
Complete Example:
public class TryCatchFinallyExample {
public static void main(String[] args) {
try {
int num = 10;
int result = num / 0; // This will throw ArithmeticException
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Exception caught: " + e.getMessage());
} finally {
System.out.println("This block always executes.");
}
}
}
Output:
Exception caught: / by zero
This block always executes.
Key Points:
- The try block must be followed by either a catch or a finally block (or both).
- If no exception occurs:
- catch is skipped.
- finally is still executed.
- If an exception occurs:
- The matching catch block handles it.
- Then finally executes.
- If an exception occurs and is not caught, the program still runs the finally block before termination.
8. Working of try-catch Block
public class Example {
public static void main(String[] args) {
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
}
}
}
Output:
Cannot divide by zero
9. The finally Block
- Used to execute important code such as closing resources.
- Executes regardless of whether an exception is caught or not.
try {
int data = 10 / 2;
} catch (Exception e) {
System.out.println(e);
} finally {
System.out.println("finally block executed");
}
Output:
finally block executed
10. Multiple catch Blocks
Java allows multiple catch blocks to handle different types of exceptions that may occur in a try block. When an exception is thrown, only the first matching catch block is executed. In the example, an ArithmeticException occurs first, so that block is executed before checking others.
try {
int arr[] = new int[5];
arr[5] = 30 / 0;
} catch (ArithmeticException e) {
System.out.println("Arithmetic Exception");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array Index Out Of Bounds Exception");
}
Output:
Arithmetic Exception
11. Nested try-catch Blocks
Java supports nesting of try-catch blocks. This is useful when a specific piece of code needs its own error handling inside a larger block. The inner try handles its exception independently, and the outer catch is triggered only if an exception is not caught by the inner block.
try {
try {
int result = 50 / 0;
} catch (ArithmeticException e) {
System.out.println("Inner catch: " + e);
}
} catch (Exception e) {
System.out.println("Outer catch: " + e);
}
Output:
Inner catch: java.lang.ArithmeticException: / by zero
12. throw Keyword
The throw keyword is used to explicitly throw an exception from a method or block of code. It is typically used for custom logic or error signaling. In the example, an ArithmeticException is manually thrown with a custom message.
public class Test {
public static void main(String[] args) {
throw new ArithmeticException("Demo Exception");
}
}
Output:
Exception in thread "main" java.lang.ArithmeticException: Demo Exception
13. throws Keyword
The throws keyword is used in method declarations to indicate that the method might throw one or more exceptions. It informs the calling code to handle or propagate those exceptions. This is mostly used for checked exceptions like IOException.
void readFile() throws IOException {
FileReader fr = new FileReader("file.txt");
}
14. Custom Exceptions (User-defined)
Java allows users to define their own exception classes by extending the Exception class. This is useful for application-specific error handling. The custom exception can carry a meaningful message to make debugging easier, as shown in the example.
class MyException extends Exception {
MyException(String message) {
super(message);
}
}
public class Demo {
public static void main(String[] args) throws MyException {
throw new MyException("Custom Exception occurred");
}
}
Output:
Exception in thread "main" MyException: Custom Exception occurred
How to Create User-Defined (Custom) Exceptions in Java
Java allows you to define your own exceptions when the standard ones do not suit your application's error handling needs. This helps in making your code more readable and specific to the business logic.
Steps to Create a User-Defined Exception
Step 1: Create a class that extends Exception or RuntimeException
class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
- Extend Exception for a checked exception (must be handled with try-catch or throws).
- Extend RuntimeException for an unchecked exception (not mandatory to handle).
Step 2: Use the throw keyword to throw the exception
throw new MyException("This is a custom exception");
Step 3: If it's a checked exception, declare it using throws in the method signature
public void process() throws MyException {
// some code
}
Step 4: Handle it using try-catch
try {
process();
} catch (MyException e) {
System.out.println("Caught: " + e.getMessage());
}
When to Create Custom Exceptions
- When existing Java exceptions do not clearly describe the issue.
- When you need application-specific error messages and types.
- When you want better organization of exception handling in a large application.
Example Use Case: Age Verification for Voting
// Step 1: Define the custom exception
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}
// Step 2: Use the custom exception in logic
public class VoteEligibility {
static void checkAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("You must be at least 18 years old to vote.");
} else {
System.out.println("You are eligible to vote.");
}
}
// Step 3: Handle the exception
public static void main(String[] args) {
try {
checkAge(15);
} catch (InvalidAgeException e) {
System.out.println("Exception caught: " + e.getMessage());
}
}
}
Output:
Exception caught: You must be at least 18 years old to vote.
If the age is changed to 20, the output would be:
You are eligible to vote.
Exception Handling Enhancements in Java 8
Although Java 8 did not introduce new exception types, it brought functional programming features that improved how developers write cleaner and more concise error-handling code. These include:
1. Using Lambda Expressions with Functional Interfaces
In Java 8, you can use lambda expressions to handle exceptions within custom functional interfaces.
Example: Functional Interface with Exception Handling
@FunctionalInterface
interface CheckedFunction {
R apply(T t) throws Exception;
}
public class LambdaExceptionHandling {
public static void main(String[] args) {
CheckedFunction parse = s -> Integer.parseInt(s);
try {
System.out.println(parse.apply("123")); // Output: 123
System.out.println(parse.apply("abc")); // Throws NumberFormatException
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
}
}
}
This allows for cleaner and reusable handling of methods that may throw exceptions, especially when working with streams or APIs.
2. Exception Handling in Streams
Streams in Java 8 don’t allow checked exceptions directly inside lambda expressions. To handle them, you can:
a. Wrap checked exceptions in a RuntimeException
List list = Arrays.asList("10", "20", "abc");
list.stream().forEach(item -> {
try {
int number = Integer.parseInt(item);
System.out.println(number);
} catch (NumberFormatException e) {
System.out.println("Invalid number: " + item);
}
});
b. Use a utility wrapper for lambdas that throw exceptions
public static Consumer throwSafe(CheckedFunction function) {
return i -> {
try {
function.apply(i);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
This is particularly useful when mapping over streams with methods that throw checked exceptions.
3. Optional for Handling NullPointerException
Java 8 introduced Optional as a container object which may or may not contain a non-null value. This helps to avoid NullPointerException, one of the most common unchecked exceptions.
Example:
public class OptionalExample {
public static void main(String[] args) {
String name = null;
Optional optionalName = Optional.ofNullable(name);
System.out.println(optionalName.orElse("Default Name")); // Output: Default Name
}
}
Instead of explicitly checking for null and throwing a NullPointerException, Optional provides a cleaner and safer approach.
4. CompletableFuture and Exception Handling in Async Code
Java 8 introduced CompletableFuture for writing asynchronous, non-blocking code with support for exception handling.
Example:
import java.util.concurrent.*;
public class FutureExceptionHandling {
public static void main(String[] args) {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Something went wrong!");
}
return "Success";
}).exceptionally(ex -> {
System.out.println("Handled: " + ex.getMessage());
return "Recovered";
});
System.out.println(future.join()); // Output: Handled: Something went wrong! Recovered
}
}
This approach is clean, and it keeps exception handling separate from the main logic, especially for async tasks.
15. Common Built-in Exceptions
- ArithmeticException
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassNotFoundException
- IOException
- NumberFormatException
16. Best Practices in Exception Handling
- Catch specific exceptions first.
- Avoid catching generic Exception unless necessary.
- Never ignore exceptions (e.g., empty catch blocks).
- Always clean up resources in finally or use try-with-resources.
- Throw custom exceptions with meaningful names and messages.
17. Real-World Analogy for Exception Handling
Imagine withdrawing money from an ATM. If the network fails or your card is invalid, the machine shows a proper error message and doesn’t crash. This is similar to how Java handles exceptions.
18. Advantages of Exception Handling
- Separates error-handling code from regular logic.
- Promotes graceful application termination.
- Enhances code robustness and reliability.
19. Disadvantages of Improper Handling
- Swallowing exceptions hides bugs.
- Generic catch blocks may mask important details.
- Overuse of try-catch can clutter code.
Conclusion
Exception handling in Java is a crucial mechanism that ensures your programs can deal with unexpected situations gracefully without crashing. By using constructs like try, catch, finally, throw, and throws, Java allows developers to anticipate errors, handle them properly, and maintain the normal flow of execution.
Understanding the types of exceptions (checked, unchecked, and errors), the exception hierarchy, and how to use try-catch-finally blocks helps in writing robust, maintainable, and user-friendly applications.
It not only improves the user experience by providing meaningful error messages but also enhances the stability and reliability of your code.
In short, exception handling is not just about fixing problems — it's about writing programs that expect the unexpected and respond to it with confidence and control.