Exception Handling in Java
Learn Exception Handling in Java in detail, including exceptions, errors, exception hierarchy, try-catch, finally, throw, throws, checked and unchecked exceptions, custom exceptions, try-with-resources, exception propagation, and best practices.
Introduction
Exception handling is the mechanism used to detect, represent, and respond to unexpected situations that occur while a Java program is running. It allows a program to handle failures in a controlled manner instead of terminating immediately.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println(
"Cannot divide by zero"
);
}Without exception handling, the division by zero would terminate the normal flow of the program. The catch block allows the program to respond to the problem.
- What exceptions are.
- Why exception handling is required.
- The difference between normal and exceptional flow.
- The Java exception hierarchy.
- What Throwable represents.
- The difference between errors and exceptions.
- What checked exceptions are.
- What unchecked exceptions are.
- Common runtime exceptions.
- How the try block works.
- How the catch block works.
- How exception objects work.
- How multiple catch blocks work.
- How multi-catch works.
- How nested exception handling works.
- How the finally block works.
- How throw works.
- How throws works.
- The difference between throw and throws.
- How exceptions propagate through methods.
- How to rethrow exceptions.
- How exception chaining works.
- How to create custom exceptions.
- How try-with-resources works.
- How AutoCloseable works.
- How exceptions interact with overriding.
- How to translate exceptions between application layers.
- How to log exceptions correctly.
- How to design robust exception handling.
What is an Exception?
An exception is an object that represents an abnormal condition that occurs during program execution. When an exceptional condition occurs, Java creates or receives an exception object and changes the normal flow of execution.
NORMAL EXECUTION
Statement 1
│
▼
Statement 2
│
▼
PROBLEM OCCURS
│
▼
Exception Object Created
│
▼
Normal Flow Interrupted
│
▼
Matching Handler Searched
│
├── Found ──► catch Block
│
└── Not Found ──► Program Terminates- An unexpected condition occurs.
- Java represents the condition using an object.
- Normal execution may stop at that point.
- Java searches for a matching exception handler.
- A matching catch block can respond to the problem.
Why Exception Handling?
Prevent Sudden Failure
Expected failure conditions can be handled without immediately terminating the entire program.
Maintain Program Flow
The application can recover, retry, return a fallback, or continue safely.
Meaningful Errors
Technical failures can be converted into understandable messages.
Resource Cleanup
Files, streams, and other resources can be closed correctly.
Separation of Concerns
Normal business logic can be separated from failure-handling logic.
Debugging
Exception details and stack traces help identify the source of failures.
Real-World Analogy
Consider withdrawing money from an ATM. The normal operation is to enter an amount and receive cash. However, insufficient balance, an invalid amount, or a network failure may interrupt the normal operation.
NORMAL FLOW
Enter Amount
│
▼
Validate Account
│
▼
Check Balance
│
▼
Dispense Cash
EXCEPTIONAL FLOW
Enter Amount
│
▼
Insufficient Balance
│
▼
Handle Problem
│
▼
Show Message
│
▼
Transaction Ends SafelyNormal Flow vs Exceptional Flow
START
│
▼
Statement A
│
▼
Statement B
│
▼
Statement C
│
▼
ENDSTART
│
▼
Statement A
│
▼
Statement B
│
▼
EXCEPTION
│
├───────────────┐
│ │
▼ ▼
HANDLED UNHANDLED
│ │
▼ ▼
Continue TerminateJava Exception Hierarchy
All errors and exceptions in Java are represented by classes that ultimately inherit from Throwable.
Object
│
▼
Throwable
├── Error
│
│ ├── VirtualMachineError
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── AssertionError
│
└── Exception
├── IOException
├── SQLException
├── ClassNotFoundException
└── RuntimeException
├── ArithmeticException
├── NullPointerException
├── IndexOutOfBoundsException
├── NumberFormatException
├── ClassCastException
├── IllegalArgumentException
└── IllegalStateExceptionThrowable
Throwable is the root class for objects that can be thrown using the throw statement and handled by the Java exception mechanism.
Throwable
├── Error
│
│ Serious system-level problems
│
└── Exception
Conditions applications
may handle- Throwable includes both exceptions and serious errors.
- Catching Throwable may accidentally hide system-level failures.
- Application code normally catches specific exception types.
- Broad catches should be used only with clear architectural reasons.
Errors
Errors represent serious problems that applications generally should not attempt to recover from through ordinary exception handling.
ERRORS
├── OutOfMemoryError
│
│ JVM cannot allocate required memory
│
├── StackOverflowError
│
│ Call stack is exhausted
│
├── VirtualMachineError
│
│ JVM-level failure
│
└── AssertionError
Assertion condition failed- Errors are not the same as ordinary exceptions.
- They usually indicate serious runtime or environment problems.
- Catching an Error rarely fixes its underlying cause.
- Application code should generally focus on handling Exceptions.
Exceptions
Exceptions represent conditions that application code may be able to anticipate, report, recover from, or translate into another meaningful failure.
EXCEPTIONS
├── Checked Exceptions
│
│ Checked by compiler
│
│ Must be handled or declared
│
└── Unchecked Exceptions
RuntimeException family
Compiler does not require
handling or declarationChecked Exceptions
Checked exceptions are exceptions that the compiler requires code to handle with try-catch or declare using throws.
import java.io.FileReader;
FileReader reader =
new FileReader(
"data.txt"
);The FileReader constructor may throw FileNotFoundException. The compiler requires the program to handle or declare that possibility.
try {
FileReader reader =
new FileReader(
"data.txt"
);
} catch (FileNotFoundException e) {
System.out.println(
"File not found"
);
}void readFile()
throws FileNotFoundException {
FileReader reader =
new FileReader(
"data.txt"
);
}- IOException
- FileNotFoundException
- SQLException
- ClassNotFoundException
- InterruptedException
Unchecked Exceptions
Unchecked exceptions inherit from RuntimeException. The compiler does not require them to be caught or declared.
int result = 10 / 0;This code compiles successfully, but it throws ArithmeticException when executed.
- ArithmeticException
- NullPointerException
- ArrayIndexOutOfBoundsException
- NumberFormatException
- ClassCastException
- IllegalArgumentException
- IllegalStateException
Checked vs Unchecked Exceptions
CHECKED EXCEPTIONS
Compiler checks them
Must be caught or declared
Usually represent recoverable
external conditions
Examples:
IOException
SQLException
ClassNotFoundException
UNCHECKED EXCEPTIONS
Compiler does not require handling
RuntimeException family
Often represent programming errors,
invalid input, or invalid state
Examples:
NullPointerException
IllegalArgumentException
ArithmeticExceptionCommon Runtime Exceptions
RuntimeException
├── ArithmeticException
├── NullPointerException
├── IndexOutOfBoundsException
│ ├── ArrayIndexOutOfBoundsException
│ └── StringIndexOutOfBoundsException
├── NumberFormatException
├── ClassCastException
├── IllegalArgumentException
└── IllegalStateExceptionArithmeticException
int number = 10;
int result = number / 0;ArithmeticException:
/ by zero- Integer division by zero throws ArithmeticException.
- Floating-point division by zero follows IEEE 754 rules.
- For example, 10.0 / 0.0 produces Infinity instead of ArithmeticException.
NullPointerException
String name = null;
System.out.println(
name.length()
);The reference does not point to an object, so attempting to access an instance method throws NullPointerException.
ArrayIndexOutOfBoundsException
int[] numbers = {
10,
20,
30
};
System.out.println(
numbers[5]
);The array contains indexes 0 through 2. Accessing index 5 throws ArrayIndexOutOfBoundsException.
NumberFormatException
int number =
Integer.parseInt(
"Java"
);The text cannot be converted into an integer, so NumberFormatException is thrown.
ClassCastException
Object value = "Java";
Integer number =
(Integer) value;The actual object is a String, so it cannot be cast to Integer.
IllegalArgumentException
void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException(
"Age cannot be negative"
);
}
}IllegalArgumentException is commonly used when a method receives an argument that violates its requirements.
IllegalStateException
void start() {
if (alreadyStarted) {
throw new IllegalStateException(
"Already started"
);
}
}IllegalStateException indicates that a method call is not valid for the current state of the object.
The try Block
The try block contains code that may throw an exception.
try {
// Code that may fail
}- A normal try block must be followed by at least one catch or finally block.
- A try-with-resources statement may also manage resources automatically.
- Only code that requires exception handling should be placed inside the try block.
The catch Block
The catch block handles an exception of a specified type.
catch (ExceptionType e) {
// Handle exception
}Basic try-catch
public class Main {
public static void main(
String[] args
) {
try {
int result = 10 / 0;
System.out.println(
result
);
} catch (
ArithmeticException e
) {
System.out.println(
"Cannot divide by zero"
);
}
System.out.println(
"Program continues"
);
}
}Cannot divide by zero
Program continuesThe Exception Object
The catch parameter receives the exception object. This object contains information about the failure.
catch (ArithmeticException e) {
System.out.println(
e.getMessage()
);
}Exception Object
├── Exception Type
├── Message
├── Cause
├── Stack Trace
└── Suppressed ExceptionsCommon Exception Methods
System.out.println(
e.getMessage()
);System.out.println(
e.toString()
);e.printStackTrace();Throwable cause =
e.getCause();getMessage()
Returns only the detail message
toString()
Returns exception class
and detail message
printStackTrace()
Prints complete call history
showing where failure occurred
getCause()
Returns the underlying causeMultiple catch Blocks
try {
String input = args[0];
int number =
Integer.parseInt(
input
);
System.out.println(
100 / number
);
} catch (
ArrayIndexOutOfBoundsException e
) {
System.out.println(
"Input is missing"
);
} catch (
NumberFormatException e
) {
System.out.println(
"Input must be a number"
);
} catch (
ArithmeticException e
) {
System.out.println(
"Number cannot be zero"
);
}Only the first matching catch block executes for a thrown exception.
Catch Block Order
When multiple catch blocks are used, more specific exception types must appear before more general types.
try {
// Risky code
} catch (
NumberFormatException e
) {
// Specific
} catch (
RuntimeException e
) {
// More general
} catch (
Exception e
) {
// Most general
}try {
// Risky code
} catch (Exception e) {
// Handles everything below Exception
} catch (
NumberFormatException e
) {
// Unreachable code
}Multi-Catch
A single catch block can handle multiple unrelated exception types using the pipe character.
try {
// Risky code
} catch (
IOException |
SQLException e
) {
System.out.println(
e.getMessage()
);
}- Separate exception types using |.
- The exception types cannot have a direct parent-child relationship in the same multi-catch.
- The catch parameter is effectively final.
- Use multi-catch when handling logic is genuinely identical.
Nested try-catch
try {
System.out.println(
"Outer try"
);
try {
int result = 10 / 0;
} catch (
ArithmeticException e
) {
System.out.println(
"Inner exception handled"
);
}
} catch (Exception e) {
System.out.println(
"Outer exception handled"
);
}- Nested try-catch is valid.
- It can isolate failures in a smaller operation.
- Deep nesting makes code difficult to read.
- Extracting operations into methods is often clearer.
The finally Block
The finally block contains code intended to execute after try and catch processing, whether an exception occurs or not.
try {
// Risky code
} catch (Exception e) {
// Handle exception
} finally {
// Cleanup code
}try {
System.out.println(
10 / 2
);
} catch (
ArithmeticException e
) {
System.out.println(
"Error"
);
} finally {
System.out.println(
"Finally executed"
);
}finally Execution Rules
NO EXCEPTION
try
│
▼
finally
HANDLED EXCEPTION
try
│
▼
catch
│
▼
finally
UNHANDLED EXCEPTION
try
│
▼
finally
│
▼
Exception Propagates- finally normally executes.
- It may not execute if the JVM terminates abruptly.
- System.exit() can prevent normal finally execution.
- A severe process or machine failure can also prevent execution.
- Avoid return statements inside finally because they can hide results and exceptions.
try-finally Without catch
try {
System.out.println(
"Processing"
);
} finally {
System.out.println(
"Cleanup"
);
}A catch block is not mandatory when a finally block follows the try block. If an exception is not handled, it propagates after finally executes.
The throw Keyword
The throw keyword explicitly throws a specific exception object.
throw new ExceptionType(
"Message"
);void withdraw(
double amount
) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Amount must be positive"
);
}
}Throwing Built-in Exceptions
if (age < 0) {
throw new IllegalArgumentException(
"Age cannot be negative"
);
}if (!connected) {
throw new IllegalStateException(
"Connection is not active"
);
}if (name == null) {
throw new NullPointerException(
"Name cannot be null"
);
}- Use IllegalArgumentException for invalid method arguments.
- Use IllegalStateException for invalid object state.
- Use specific standard exceptions when their meaning matches the problem.
- Create custom exceptions for meaningful domain-specific failures.
The throws Keyword
The throws keyword declares that a method may allow one or more exceptions to propagate to its caller.
returnType methodName()
throws ExceptionType {
// Method code
}void readFile()
throws IOException {
FileReader reader =
new FileReader(
"data.txt"
);
}void process()
throws IOException,
SQLException {
// Code
}throw vs throws
throw
Used inside a method or block
Throws one exception object
Example:
throw new
IllegalArgumentException();
throws
Used in method declaration
Declares possible exception types
Example:
void read()
throws IOExceptionException Propagation
Exception propagation occurs when a method does not handle an exception and the exception moves up the call stack to the caller.
main()
│ calls
▼
methodA()
│ calls
▼
methodB()
│
▼
EXCEPTION
│
▼
methodB has no handler
│
▼
methodA searched
│
▼
main searched
│
├── Handler Found ──► Handle
│
└── No Handler ──► JVM TerminatesException Propagation Example
public class Main {
static void methodC() {
int result = 10 / 0;
}
static void methodB() {
methodC();
}
static void methodA() {
methodB();
}
public static void main(
String[] args
) {
try {
methodA();
} catch (
ArithmeticException e
) {
System.out.println(
"Exception handled in main"
);
}
}
}methodC()
throws exception
│
▼
methodB()
no handler
│
▼
methodA()
no handler
│
▼
main()
handler foundHandling vs Declaring Exceptions
void readFile() {
try {
Files.readString(
Path.of("data.txt")
);
} catch (IOException e) {
System.out.println(
"Unable to read file"
);
}
}void readFile()
throws IOException {
Files.readString(
Path.of("data.txt")
);
}- Handle an exception when the current method can respond meaningfully.
- Declare an exception when the caller is better positioned to decide what to do.
- Do not catch an exception only to ignore it.
- Do not declare every exception without considering API design.
Rethrowing Exceptions
try {
process();
} catch (IOException e) {
System.out.println(
"Processing failed"
);
throw e;
}Rethrowing allows a method to perform local work such as logging or cleanup while still allowing the caller to handle the failure.
Exception Chaining
Exception chaining wraps one exception inside another while preserving the original cause.
try {
repository.save(user);
} catch (SQLException e) {
throw new UserStorageException(
"Unable to save user",
e
);
}UserStorageException
"Unable to save user"
│
▼
CAUSE
SQLException
"Database connection failed"- The higher layer receives a meaningful exception.
- The original technical failure remains available.
- Debugging information is preserved.
- Logs can show the complete chain of failure.
Custom Exceptions
A custom exception is an application-specific exception class created to represent a meaningful failure in a particular domain.
class InsufficientBalanceException
extends RuntimeException {
public InsufficientBalanceException(
String message
) {
super(message);
}
}if (amount > balance) {
throw new
InsufficientBalanceException(
"Insufficient balance"
);
}Custom Checked Exception
public class
InvalidDocumentException
extends Exception {
public InvalidDocumentException(
String message
) {
super(message);
}
}void processDocument(
String document
) throws InvalidDocumentException {
if (document == null) {
throw new
InvalidDocumentException(
"Document is required"
);
}
}Custom Unchecked Exception
public class InvalidOrderException
extends RuntimeException {
public InvalidOrderException(
String message
) {
super(message);
}
}if (order == null) {
throw new InvalidOrderException(
"Order is required"
);
}- Use a checked exception when callers are expected to recover and should be forced to consider the failure.
- Use an unchecked exception for invalid arguments, invalid state, business rule violations, and failures that callers cannot usually recover from locally.
- Choose based on API semantics rather than habit.
Custom Exception Constructors
public class PaymentException
extends RuntimeException {
public PaymentException() {
super();
}
public PaymentException(
String message
) {
super(message);
}
public PaymentException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
public PaymentException(
Throwable cause
) {
super(cause);
}
}Try-with-Resources
Try-with-resources automatically closes resources that implement AutoCloseable.
BufferedReader reader = null;
try {
reader = new BufferedReader(
new FileReader(
"data.txt"
)
);
System.out.println(
reader.readLine()
);
} finally {
if (reader != null) {
reader.close();
}
}try (
BufferedReader reader =
new BufferedReader(
new FileReader(
"data.txt"
)
)
) {
System.out.println(
reader.readLine()
);
}- Resources close automatically.
- Cleanup code is shorter.
- The code is easier to read.
- Resources close even when exceptions occur.
- Suppressed exceptions are preserved.
AutoCloseable
A resource can be used in try-with-resources when its type implements AutoCloseable.
class DatabaseConnection
implements AutoCloseable {
public void execute() {
System.out.println(
"Executing query"
);
}
@Override
public void close() {
System.out.println(
"Connection closed"
);
}
}try (
DatabaseConnection connection =
new DatabaseConnection()
) {
connection.execute();
}Multiple Resources
try (
BufferedReader reader =
new BufferedReader(
new FileReader(
"input.txt"
)
);
BufferedWriter writer =
new BufferedWriter(
new FileWriter(
"output.txt"
)
)
) {
writer.write(
reader.readLine()
);
}- Resources are closed automatically.
- They are closed in reverse order of creation.
- The writer closes before the reader in this example.
Suppressed Exceptions
When both the main operation and resource closing throw exceptions, try-with-resources preserves the main exception and attaches closing failures as suppressed exceptions.
catch (Exception e) {
System.out.println(
"Main: "
+ e.getMessage()
);
for (
Throwable suppressed :
e.getSuppressed()
) {
System.out.println(
"Suppressed: "
+ suppressed.getMessage()
);
}
}Exception Handling in Methods
void process() {
try {
readFile();
} catch (IOException e) {
System.out.println(
"Unable to process file"
);
}
}void process()
throws IOException {
readFile();
}Exception Handling and Method Overriding
Overriding methods follow specific rules for checked exceptions.
class Parent {
void process()
throws IOException {
}
}class Child extends Parent {
@Override
void process()
throws FileNotFoundException {
}
}- An overriding method may throw the same checked exception.
- It may throw a narrower checked exception.
- It may throw no checked exception.
- It cannot add a broader checked exception than the parent method allows.
- Unchecked exceptions are not restricted by the same checked-exception rule.
Exceptions in Constructors
class User {
private final String name;
User(String name) {
if (
name == null ||
name.isBlank()
) {
throw new IllegalArgumentException(
"Name is required"
);
}
this.name = name;
}
}If a constructor throws an exception, object construction does not complete successfully.
Exceptions in Static Blocks
class Configuration {
static {
if (loadFailed()) {
throw new
ExceptionInInitializerError(
"Configuration failed"
);
}
}
}- Static initialization happens when a class is initialized.
- Checked exceptions cannot simply escape a static initializer.
- Failures can prevent the class from initializing correctly.
- Avoid complex failure-prone work in static initialization.
Assertions vs Exceptions
ASSERTIONS
Used for internal assumptions
May be disabled
Not for normal user input validation
Example:
assert value > 0;
EXCEPTIONS
Used for runtime failures
Always part of program behavior
Used for invalid input,
state, I/O, database failures,
and business rule violationsValidation vs Exceptions
Validation checks expected input conditions before performing an operation. Exceptions represent failures or invalid conditions that prevent normal completion.
if (
email == null ||
email.isBlank()
) {
return false;
}if (account == null) {
throw new IllegalArgumentException(
"Account is required"
);
}- Use normal conditions for expected branching.
- Use validation for expected user input checks.
- Use exceptions when an operation cannot complete normally.
- Do not use exceptions as a replacement for ordinary control flow.
Fail Fast Principle
Fail fast means detecting invalid conditions as early as possible instead of allowing corrupted or invalid state to move deeper into the program.
public void transfer(
Account from,
Account to,
double amount
) {
if (from == null) {
throw new IllegalArgumentException(
"Source account is required"
);
}
if (to == null) {
throw new IllegalArgumentException(
"Target account is required"
);
}
if (amount <= 0) {
throw new IllegalArgumentException(
"Amount must be positive"
);
}
// Continue only with valid input
}Exception Translation
Exception translation converts a low-level technical exception into a higher-level exception that is meaningful to the current application layer.
try {
database.insert(user);
} catch (SQLException e) {
throw new UserStorageException(
"Unable to store user",
e
);
}DATABASE LAYER
SQLException
│
▼
REPOSITORY LAYER
UserStorageException
│
▼
SERVICE LAYER
Meaningful application handlingLogging Exceptions
catch (Exception e) {
System.out.println(
"Something went wrong"
);
}catch (PaymentException e) {
logger.error(
"Payment failed for order {}",
orderId,
e
);
}- Log enough context to understand the failed operation.
- Preserve the exception object so the stack trace remains available.
- Do not log sensitive information such as passwords or card details.
- Avoid logging the same exception repeatedly at every layer.
- Do not replace proper logging with printStackTrace() in production applications.
Banking Example
public class
InsufficientBalanceException
extends RuntimeException {
public InsufficientBalanceException(
String message
) {
super(message);
}
}public class BankAccount {
private final String
accountNumber;
private double balance;
public BankAccount(
String accountNumber,
double balance
) {
if (
accountNumber == null ||
accountNumber.isBlank()
) {
throw new IllegalArgumentException(
"Account number is required"
);
}
if (balance < 0) {
throw new IllegalArgumentException(
"Balance cannot be negative"
);
}
this.accountNumber =
accountNumber;
this.balance = balance;
}
public void withdraw(
double amount
) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Amount must be positive"
);
}
if (amount > balance) {
throw new
InsufficientBalanceException(
"Insufficient balance"
);
}
balance -= amount;
}
public double getBalance() {
return balance;
}
}public class Main {
public static void main(
String[] args
) {
BankAccount account =
new BankAccount(
"ACC1001",
10000
);
try {
account.withdraw(
15000
);
} catch (
InsufficientBalanceException e
) {
System.out.println(
e.getMessage()
);
}
}
}User Registration Example
public class DuplicateEmailException
extends RuntimeException {
public DuplicateEmailException(
String message
) {
super(message);
}
}public class UserService {
private final UserRepository
userRepository;
public UserService(
UserRepository userRepository
) {
this.userRepository =
userRepository;
}
public void register(
String email
) {
if (
email == null ||
email.isBlank()
) {
throw new IllegalArgumentException(
"Email is required"
);
}
if (
userRepository.existsByEmail(
email
)
) {
throw new DuplicateEmailException(
"Email already registered"
);
}
userRepository.save(
new User(email)
);
}
}File Processing Example
import java.io.*;
import java.nio.file.*;
public class FileProcessor {
public String read(
String filename
) {
if (
filename == null ||
filename.isBlank()
) {
throw new IllegalArgumentException(
"Filename is required"
);
}
try {
return Files.readString(
Path.of(filename)
);
} catch (
NoSuchFileException e
) {
throw new FileProcessingException(
"File does not exist: "
+ filename,
e
);
} catch (IOException e) {
throw new FileProcessingException(
"Unable to read file: "
+ filename,
e
);
}
}
}public class
FileProcessingException
extends RuntimeException {
public FileProcessingException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}Payment Example
public class PaymentException
extends RuntimeException {
public PaymentException(
String message
) {
super(message);
}
public PaymentException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}public class PaymentService {
private final PaymentGateway
paymentGateway;
public PaymentService(
PaymentGateway paymentGateway
) {
this.paymentGateway =
paymentGateway;
}
public void pay(
double amount
) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Amount must be positive"
);
}
try {
paymentGateway.process(
amount
);
} catch (
GatewayConnectionException e
) {
throw new PaymentException(
"Payment provider unavailable",
e
);
}
}
}Service Layer Example
CONTROLLER
Handles application response
│
▼
SERVICE
Throws business exceptions
│
▼
REPOSITORY
Translates persistence failures
│
▼
DATABASE
Throws technical exceptionspublic User findById(long id) {
try {
return database.find(id);
} catch (SQLException e) {
throw new DataAccessException(
"Unable to find user: "
+ id,
e
);
}
}public User getUser(long id) {
User user =
userRepository.findById(id);
if (user == null) {
throw new UserNotFoundException(
"User not found: "
+ id
);
}
return user;
}Common Exception Handling Errors
EXCEPTION HANDLING ERRORS
├── Empty catch Blocks
├── Catching Exception Everywhere
├── Catching Throwable
├── Catching Error Without Reason
├── Catching Too Broadly
├── Wrong catch Block Order
├── Unreachable catch Blocks
├── Ignoring Exceptions
├── Swallowing Exceptions
├── Logging Without Context
├── Logging Sensitive Data
├── Logging the Same Exception Repeatedly
├── Using printStackTrace() in Production
├── Returning null After Every Failure
├── Returning False Without Failure Details
├── Throwing Generic Exception
├── Throwing RuntimeException Everywhere
├── Creating Meaningless Custom Exceptions
├── Losing the Original Cause
├── Wrapping Without Passing the Cause
├── Using Exceptions for Normal Control Flow
├── Catching Exceptions Too Early
├── Handling Where Recovery Is Impossible
├── Declaring throws Exception
├── Declaring Unnecessary Exceptions
├── Forgetting Checked Exception Requirements
├── Confusing throw and throws
├── Throwing null
├── Returning from finally
├── Throwing from finally
├── Hiding the Original Exception in finally
├── Manual Resource Closing Errors
├── Forgetting to Close Resources
├── Closing Resources in Wrong Order
├── Ignoring Try-with-Resources
├── Catching and Immediately Rethrowing Without Purpose
├── Exposing Database Exceptions to UI Layers
├── Exposing Technical Details to Users
├── Using Wrong Exception Types
├── Throwing NullPointerException for Normal Validation
├── Using Checked Exceptions for Every Failure
├── Using Unchecked Exceptions for Every Failure
├── Deeply Nested try-catch Blocks
├── Huge try Blocks
├── Mixing Business Logic and Recovery Logic
├── Ignoring Exception Propagation
├── Losing Stack Trace Information
├── Creating New Exception Without Cause
├── Duplicate Exception Messages
├── Poor Error Messages
├── Missing Context in Error Messages
├── Catching Parent Before Child
├── Invalid Multi-Catch Relationships
├── Assuming finally Always Executes
├── Assuming catch Always Continues Safely
├── Retrying Without Limits
├── Infinite Retry Loops
├── Retrying Non-Retryable Failures
├── Ignoring InterruptedException
├── Using Exceptions Instead of Validation
├── Failing Too Late
├── Continuing with Invalid State
├── Exposing Internal Paths
├── Exposing Credentials in Logs
└── Treating All Exceptions the SameBest Practices
- Catch the most specific exception possible.
- Place specific catch blocks before general catch blocks.
- Do not catch Throwable without a strong reason.
- Do not catch Error as part of normal application recovery.
- Never leave catch blocks empty.
- Do not silently ignore exceptions.
- Handle an exception only when meaningful action is possible.
- Allow exceptions to propagate when the caller can handle them better.
- Use checked exceptions when callers are expected to recover.
- Use unchecked exceptions for invalid arguments and invalid state.
- Choose exception types that accurately describe the problem.
- Use IllegalArgumentException for invalid arguments.
- Use IllegalStateException for invalid object state.
- Create custom exceptions for meaningful domain failures.
- Give custom exceptions clear names.
- End custom exception names with Exception.
- Preserve the original cause when wrapping exceptions.
- Use exception chaining.
- Include useful context in exception messages.
- Do not expose sensitive data in messages.
- Do not expose internal technical details directly to end users.
- Separate user-facing messages from diagnostic information.
- Use try-with-resources for AutoCloseable resources.
- Prefer automatic resource management over manual cleanup.
- Keep try blocks small.
- Place only relevant risky operations inside try blocks.
- Avoid deeply nested try-catch structures.
- Extract complex operations into methods.
- Do not use exceptions for normal control flow.
- Use normal conditions for expected branching.
- Validate predictable input conditions early.
- Follow the fail-fast principle.
- Do not allow invalid state to spread through the application.
- Avoid return statements in finally.
- Avoid throwing new exceptions from finally.
- Do not hide an existing exception during cleanup.
- Understand that finally is not guaranteed after abrupt JVM termination.
- Use multi-catch only when handling logic is identical.
- Do not combine parent and child exception types in one multi-catch.
- Document checked exceptions in public APIs.
- Avoid throws Exception in normal API design.
- Do not declare unnecessary exceptions.
- Respect checked exception rules when overriding methods.
- Use exception translation between architectural layers.
- Do not leak low-level persistence exceptions into business layers.
- Do not leak infrastructure exceptions into user interfaces.
- Log exceptions with useful operational context.
- Pass the exception object to the logging system.
- Avoid logging the same exception at every layer.
- Choose one appropriate layer to log each failure.
- Never log passwords, tokens, or sensitive payment information.
- Do not use printStackTrace() as production logging.
- Use retry only for failures that may succeed later.
- Limit retry attempts.
- Use delay or backoff for external-service retries.
- Do not retry validation failures.
- Do not retry permanent business-rule failures.
- Preserve interruption status when handling InterruptedException appropriately.
- Test exception paths.
- Test invalid input.
- Test resource cleanup.
- Test exception translation.
- Test original causes.
- Keep exception hierarchies simple.
- Avoid creating one custom exception for every minor condition.
- Use meaningful domain exceptions.
- Design exception behavior as part of the API contract.
- Make failures understandable.
- Make failures diagnosable.
- Make recovery explicit.
- Prefer prevention over unnecessary catching.
- Use exception handling to improve reliability, not to hide defects.
Common Misconceptions
- Exceptions and errors are not the same.
- All exceptions ultimately inherit from Throwable.
- RuntimeException is an Exception.
- Checked exceptions must be caught or declared.
- Unchecked exceptions do not require catch or throws.
- Unchecked does not mean unimportant.
- Catching an exception does not automatically fix the problem.
- A catch block should not always continue execution.
- The finally block normally executes whether an exception occurs or not.
- finally is not guaranteed after abrupt JVM termination.
- A try block can have multiple catch blocks.
- Only the first matching catch block executes.
- Specific catches must appear before general catches.
- throw and throws are different.
- throw throws an exception object.
- throws declares possible exception types.
- Exceptions can propagate through method calls.
- An exception can be rethrown.
- Wrapping an exception should preserve its cause.
- Custom exceptions can be checked or unchecked.
- Not every application failure needs a custom exception.
- Try-with-resources automatically closes resources.
- Resources close in reverse order.
- finally is not the preferred resource-management technique when try-with-resources applies.
- Exception handling should not replace input validation.
- Exceptions should not be used for ordinary loops and branching.
- Catching Exception everywhere is not good defensive programming.
- Empty catch blocks are dangerous.
- Logging an exception at every layer is not always useful.
- printStackTrace() is not a production logging strategy.
- Checked exceptions are not always better than unchecked exceptions.
- Unchecked exceptions are not always programming bugs.
- Good exception design depends on API and domain semantics.
Practice Exercises
- Accept two integer values.
- Divide the first by the second.
- Handle division by zero.
- Display a meaningful message.
- Continue the program after handling.
- Accept a String value.
- Convert it to an integer.
- Handle NumberFormatException.
- Display the invalid input.
- Return a safe result.
- Read a command-line argument.
- Convert it to an integer.
- Divide 100 by the value.
- Handle missing input.
- Handle invalid numbers.
- Handle division by zero.
- Create InsufficientBalanceException.
- Create a BankAccount class.
- Validate withdrawal amounts.
- Throw the custom exception when balance is insufficient.
- Handle the exception in calling code.
- Create InvalidDocumentException extending Exception.
- Create a document-processing method.
- Throw the exception for invalid documents.
- Handle or declare the exception.
- Compare both approaches.
- Create a text file.
- Read the file using BufferedReader.
- Use try-with-resources.
- Handle IOException.
- Verify automatic resource closing.
- Create a repository method.
- Simulate a low-level database exception.
- Create a custom DataAccessException.
- Wrap the original exception.
- Preserve the cause.
- Create repository, service, and application layers.
- Throw a technical exception in the repository.
- Translate it into an application exception.
- Handle the final exception at the application boundary.
- Avoid duplicate logging.
Common Interview Questions
What is an exception in Java?
An exception is an object representing an abnormal condition that interrupts the normal flow of program execution.
What is the difference between Error and Exception?
Errors generally represent serious system-level problems, while exceptions represent conditions applications may be able to handle.
What is the difference between checked and unchecked exceptions?
Checked exceptions must be caught or declared, while unchecked exceptions do not have that compiler requirement.
What is the root class of Java exceptions and errors?
Throwable.
What is the purpose of try?
The try block contains code that may throw an exception.
What is the purpose of catch?
The catch block handles a matching exception.
What is the purpose of finally?
The finally block normally executes after try and catch processing and is traditionally used for cleanup.
What is the difference between throw and throws?
throw explicitly throws an exception object, while throws declares possible exceptions in a method signature.
Can we have try without catch?
Yes. A try block can be followed by finally without a catch block.
Can we have multiple catch blocks?
Yes. They must be ordered from more specific exception types to more general types.
What is exception propagation?
It is the movement of an unhandled exception up the method call stack.
What is exception chaining?
Exception chaining preserves one exception as the cause of another higher-level exception.
What is a custom exception?
A custom exception is an application-specific exception class representing a meaningful domain or application failure.
What is try-with-resources?
It is a try statement that automatically closes resources implementing AutoCloseable.
In what order are multiple resources closed?
They are closed in reverse order of creation.
Can an overriding method throw a broader checked exception?
No. It may throw the same checked exception, a narrower one, or no checked exception.
Should exceptions be used for normal control flow?
No. Expected branching should normally use conditions and regular control structures.
Frequently Asked Questions
Should every exception be caught?
No. Catch an exception only when the current layer can handle, recover from, translate, or meaningfully report it.
Should I catch Exception?
Usually prefer specific exception types. Catching Exception may be appropriate at carefully designed application boundaries.
Can finally contain a return statement?
It can, but it should be avoided because it can override earlier return values and hide exceptions.
Should custom exceptions be checked or unchecked?
Choose based on whether callers are expected to recover and whether the API should force explicit handling.
Why preserve the original exception cause?
It preserves technical details and stack information needed to understand the complete failure.
Should I use finally to close files?
Prefer try-with-resources when the resource implements AutoCloseable.
What should I learn after Exception Handling?
The next lesson covers File Handling in Java.
Key Takeaways
- Exception handling manages abnormal runtime conditions.
- An exception is represented by an object.
- Exceptions can interrupt normal program flow.
- Java searches for a matching exception handler.
- Throwable is the root of errors and exceptions.
- Error and Exception are different branches of Throwable.
- Errors usually represent serious system-level problems.
- Exceptions represent conditions applications may handle.
- Checked exceptions must be caught or declared.
- Unchecked exceptions inherit from RuntimeException.
- Unchecked exceptions do not require catch or throws.
- ArithmeticException can occur during integer division by zero.
- NullPointerException occurs when using a null reference improperly.
- ArrayIndexOutOfBoundsException occurs for invalid array indexes.
- NumberFormatException occurs during invalid numeric conversion.
- ClassCastException occurs during incompatible casts.
- IllegalArgumentException represents invalid method arguments.
- IllegalStateException represents invalid object state.
- The try block contains risky code.
- The catch block handles matching exceptions.
- The exception object contains failure information.
- getMessage() returns the detail message.
- printStackTrace() displays the call stack.
- Multiple catch blocks can handle different exception types.
- Specific catches must appear before general catches.
- Multi-catch handles multiple unrelated exception types.
- Nested try-catch is valid but should not become excessively deep.
- finally normally executes after try and catch.
- try can be followed by finally without catch.
- throw explicitly throws an exception object.
- throws declares possible exception types.
- throw and throws have different purposes.
- Unhandled exceptions propagate through the call stack.
- Exceptions can be rethrown.
- Exception chaining preserves original causes.
- Custom exceptions represent meaningful application failures.
- Custom checked exceptions extend Exception.
- Custom unchecked exceptions extend RuntimeException.
- Custom exceptions should provide useful constructors.
- Try-with-resources automatically closes resources.
- Resources must implement AutoCloseable.
- Multiple resources close in reverse order.
- Suppressed exceptions preserve resource-closing failures.
- Overriding methods cannot add broader checked exceptions.
- Constructors can throw exceptions.
- Failed construction means the object is not created successfully.
- Assertions and exceptions serve different purposes.
- Validation and exception handling serve different purposes.
- Exceptions should not replace ordinary control flow.
- Fail-fast design detects invalid conditions early.
- Exception translation creates meaningful layer-specific failures.
- Wrapped exceptions should preserve their causes.
- Logging should include useful context.
- Sensitive information should never be logged.
- The same exception should not be logged repeatedly without reason.
- Empty catch blocks should be avoided.
- Catching Throwable should generally be avoided.
- Catching overly broad exceptions should be avoided.
- Try blocks should remain focused.
- Try-with-resources is preferred for resource management.
- Custom exceptions should represent meaningful concepts.
- Exception handling should make failures understandable.
- Exception handling should make failures diagnosable.
- Exception handling should support safe recovery when possible.
- Good exception design is part of good API design.
Summary
Exception handling is the Java mechanism for representing and responding to abnormal conditions during program execution. Exceptions interrupt normal flow and allow the runtime to search for matching handlers.
All exceptions and errors inherit from Throwable. Error represents serious problems that applications generally should not handle through ordinary recovery logic, while Exception represents conditions that application code may handle or propagate.
Checked exceptions must be caught or declared, while unchecked exceptions inherit from RuntimeException and do not require explicit handling or declaration.
The try block contains operations that may fail, catch handles matching exceptions, and finally normally performs work after try and catch processing. Multiple catch blocks, multi-catch, and nested handling support different exception-handling structures.
The throw keyword explicitly throws an exception object, while throws declares possible exception types in a method signature. Unhandled exceptions can propagate through the method call stack.
Custom exceptions allow applications to represent meaningful domain failures. Exception chaining preserves the original cause when low-level exceptions are translated into higher-level exceptions.
Try-with-resources automatically closes AutoCloseable resources and is preferred over manual cleanup for files, streams, database resources, and similar objects.
Good exception handling catches specific exceptions, avoids empty handlers, preserves causes, uses meaningful messages, keeps try blocks focused, avoids exceptions for normal control flow, manages resources safely, and translates failures appropriately between application layers.
- You understand what exceptions are.
- You understand why exception handling is required.
- You understand normal and exceptional flow.
- You understand the Java exception hierarchy.
- You understand Throwable.
- You know the difference between errors and exceptions.
- You understand checked exceptions.
- You understand unchecked exceptions.
- You know common runtime exceptions.
- You can use try blocks.
- You can use catch blocks.
- You understand exception objects.
- You can use exception methods.
- You can create multiple catch blocks.
- You understand catch block ordering.
- You can use multi-catch.
- You understand nested try-catch.
- You can use finally.
- You understand finally execution rules.
- You can use try-finally.
- You can use throw.
- You can throw built-in exceptions.
- You can use throws.
- You know the difference between throw and throws.
- You understand exception propagation.
- You know when to handle and when to declare exceptions.
- You can rethrow exceptions.
- You understand exception chaining.
- You can create custom exceptions.
- You can create checked custom exceptions.
- You can create unchecked custom exceptions.
- You can design custom exception constructors.
- You can use try-with-resources.
- You understand AutoCloseable.
- You can manage multiple resources.
- You understand suppressed exceptions.
- You understand exceptions in methods.
- You understand exception rules in overriding.
- You understand exceptions in constructors.
- You understand static initialization failures.
- You know the difference between assertions and exceptions.
- You know the difference between validation and exceptions.
- You understand fail-fast design.
- You understand exception translation.
- You understand exception logging principles.
- You can design practical exception handling.
- You can identify common exception handling errors.
- You know exception handling best practices.
- You are ready to learn File Handling in Java.