Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,051visitors

Built for engineers who want to go deep.


← Java Core Fundamentals

Object-Oriented Programming

  • Classes and Objects
  • Inheritance and Polymorphism
  • Interfaces and Abstract Classes

Collections Framework

  • List, Set, and Map
  • Generics
  • Collections Framework Recap
  • HashMap Deep Dive
  • TreeMap & LinkedHashMap
  • Iterators & Modification Semantics

Exceptions & Best Practices

  • Exception Handling
  • equals() and hashCode()
  • String Manipulation

JVM Internals & Memory Management

  • JVM Memory Areas
  • Garbage Collection Fundamentals
  • Modern Garbage Collectors
  • Memory Leaks in Java
  • GC Tuning & Diagnostics
Chaturmind
← Java Core Fundamentals

Object-Oriented Programming

  • Classes and Objects
  • Inheritance and Polymorphism
  • Interfaces and Abstract Classes

Collections Framework

  • List, Set, and Map
  • Generics
  • Collections Framework Recap
  • HashMap Deep Dive
  • TreeMap & LinkedHashMap
  • Iterators & Modification Semantics

Exceptions & Best Practices

  • Exception Handling
  • equals() and hashCode()
  • String Manipulation

JVM Internals & Memory Management

  • JVM Memory Areas
  • Garbage Collection Fundamentals
  • Modern Garbage Collectors
  • Memory Leaks in Java
  • GC Tuning & Diagnostics
HomeLearnJavaJava Core FundamentalsExceptions & Best Practices
✓ FreeBeginner· 7 min read

Exception Handling

Handle errors gracefully — checked vs unchecked, try-with-resources, custom exceptions.

Published September 21, 2026


Exception Handling

Things go wrong while programs run: a file is missing, the network drops, input is malformed, a bug dereferences null. Java reports these situations by throwing an exception, an object describing what went wrong. The exception unwinds the call stack until some code catches it and decides what to do, or, if nothing does, the thread ends and prints a stack trace.

Good exception handling keeps error handling separate from the normal path of the code, sends failures to the place that can actually deal with them, and never hides problems.

Advertisement

The hierarchy

Throwable
├── Error                      serious JVM problems: OutOfMemoryError, StackOverflowError — don't catch
└── Exception
    ├── (checked exceptions)   IOException, SQLException, TimeoutException …
    └── RuntimeException       (unchecked) NullPointerException, IllegalArgumentException,
                                IllegalStateException, IndexOutOfBoundsException …

Checked vs unchecked

  • Checked exceptions (subclasses of Exception but not RuntimeException) must be caught or declared with throws. The compiler enforces this. They represent conditions a caller might reasonably handle: a file that doesn't exist, a network timeout.
  • Unchecked exceptions (RuntimeException and its subclasses) need no declaration. They usually indicate programming errors (null references, invalid arguments, calling methods in the wrong state) or failures the immediate caller can't do anything about.
String read(Path p) throws IOException {             // checked: callers must decide what to do
    return Files.readString(p);
}

void setAge(int age) {
    if (age < 0) throw new IllegalArgumentException("age must be ≥ 0, was " + age);   // unchecked
}

Modern Java code, and frameworks like Spring, lean heavily on unchecked exceptions. Checked exceptions force every layer to mention failures it can't handle, and they don't work well with lambdas and streams. Spring, for example, translates checked SQLExceptions into its unchecked DataAccessException hierarchy.

try / catch / finally

try {
    Order order = repository.load(id);
    process(order);
} catch (OrderNotFoundException e) {         // most specific first
    log.warn("Order {} not found", id);
    return Optional.empty();
} catch (IOException | TimeoutException e) {  // multi-catch for identical handling (Java 7)
    throw new ServiceUnavailableException("Storage unavailable", e);   // wrap, keeping the cause
} finally {
    metrics.recordAttempt();                  // runs whether or not an exception occurred
}
  • Catch blocks are checked top to bottom, and a subclass must come before its superclass, or it'd be unreachable (a compile error).
  • finally always runs, even after return or an exception, which makes it good for cleanup. Never return from finally: it silently discards any exception in flight.

try-with-resources: automatic cleanup

Files, database connections, streams and locks must be released even when errors happen. Any class implementing AutoCloseable can be used in try-with-resources (Java 7+). It's closed automatically, in reverse order of opening:

try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
    ps.setLong(1, id);
    try (ResultSet rs = ps.executeQuery()) {
        return rs.next() ? rs.getString(1) : null;
    }
}   // ps then conn are closed here — even if an exception is thrown

If both the body and close() throw, the body's exception propagates and the close failure is attached as a suppressed exception (e.getSuppressed()) instead of replacing it. That's a detail hand-written finally blocks usually get wrong. Always prefer try-with-resources over manual finally { x.close(); }.

Custom exceptions

Create your own exception types when callers need to react to a specific failure differently:

public class InsufficientFundsException extends RuntimeException {
    private final String accountId;
    private final long shortfall;

    public InsufficientFundsException(String accountId, long shortfall) {
        super("Account " + accountId + " is short by " + shortfall);
        this.accountId = accountId;
        this.shortfall = shortfall;
    }

    public String accountId() { return accountId; }
    public long shortfall()   { return shortfall; }
}
  • Name them after what went wrong in the domain, and include the data a handler needs.
  • Prefer extending RuntimeException unless you deliberately want to force callers to handle it.
  • Don't create one exception class per method. A small hierarchy per module (for example PaymentException with a few subclasses) is easier to work with.

Wrapping and the cause chain

When translating a low-level exception into a higher-level one, always pass the original as the cause:

try {
    return objectMapper.readValue(json, Order.class);
} catch (JsonProcessingException e) {
    throw new InvalidOrderPayloadException("Could not parse order JSON", e);   // `e` becomes the cause
}

The stack trace then shows both, with "Caused by: …", so the root cause isn't lost. Dropping the cause (throw new X("failed")) is one of the most damaging habits in production debugging.

Best practices

  • Never swallow exceptions silently. An empty catch block makes failures invisible. At a minimum log it, and usually rethrow or translate it.
  • Catch at the level that can do something. A repository can't decide what the user should see, and a controller or global handler can. Let exceptions propagate until a layer can recover, retry, or turn them into a response (see Global Exception Handling for web APIs).
  • Log or rethrow, not both, at every layer. Otherwise the same error appears in the logs five times. Log it once, where it's finally handled.
  • Catch specific types. catch (Exception e) also catches bugs such as NullPointerException that should crash loudly. Broad catches belong only at the top level (the thread entry point, a request handler) as a safety net.
  • Don't use exceptions for normal control flow. Constructing exceptions (with stack traces) is relatively expensive, and it obscures logic. Check map.containsKey(k) instead of catching an exception from a lookup, and return Optional for "not found" where that's an expected outcome.
  • Fail fast with clear messages. Validate arguments at method entry (Objects.requireNonNull(x, "x")) and include the offending values in the message.
  • Restore interruption. If you catch InterruptedException and can't propagate it, call Thread.currentThread().interrupt(), so the thread's cancellation status isn't lost.

Follow-up questions this topic invites — and their answers

Q: When would you use a checked exception versus an unchecked one? A: Use checked when the caller can reasonably be expected to recover, and you want the compiler to force that decision: a missing file where the user can choose another. Use unchecked for programming errors and failures the immediate caller can't meaningfully handle. Most application code today favours unchecked exceptions with centralized handling.

Q: What's the difference between final, finally and finalize? A: final is a modifier: it prevents reassignment, overriding or extension. finally is a block that always runs after try/catch. finalize() was a garbage-collection callback, now deprecated for removal because it was unpredictable and unsafe. Use try-with-resources or Cleaner instead.

Q: Does finally run if there's a return in the try block? A: Yes. The finally block runs before the method actually returns. If finally itself returns or throws, it overrides the original return value or exception, which is why returning from finally is considered a bug.

Q: What are suppressed exceptions? A: In try-with-resources, if the body throws and then closing a resource also throws, the close exception is attached to the primary exception as "suppressed" rather than replacing it. You can see it via getSuppressed() and in the stack trace, so the original failure isn't masked.

Q: Should you catch Throwable or Error? A: Generally no. Errors like OutOfMemoryError and StackOverflowError indicate the JVM may be in a bad state, and recovery is rarely safe. The narrow exception is top-level infrastructure code that logs something and then exits or rethrows.

Advertisement

Previous

Iterators & Modification Semantics

Next

equals() and hashCode()

AI Tutor

Lesson: Exception Handling

Quick actions

AI responses can be inaccurate. Verify critical information.