Exceptions

15 questions found

What is the difference between checked and unchecked exceptions in Java, and how does the compiler treat each differently?

Beginner
Checked exceptions (subclasses of Exception but not RuntimeException, like IOException) must either be caught or explicitly declared in a method's throws clause, enforced by the compiler at compile time -- representing conditions a well-behaved caller should anticipate and handle; unchecked exceptions (subclasses of RuntimeException, like NullPointerException or IllegalArgumentException) require no such compile-time declaration or handling, typically representing programming errors or conditions the caller usually can't reasonably recover from, letting them propagate freely up the call stack without cluttering every intermediate method's signature.
// Checked exception -- must be caught or declared
public void readFile(String path) throws IOException {
    Files.readString(Path.of(path));  // IOException is checked, compiler enforces handling
}

// Unchecked exception -- no compiler enforcement
public int divide(int a, int b) {
    return a / b;  // ArithmeticException is unchecked, no throws declaration required
}
Real-world example A file-processing method's throws IOException declaration forces every calling method up the chain to either handle the exception or explicitly propagate it further, ensuring file I/O failures can never be silently ignored by accident, unlike an unchecked NullPointerException which requires no such compile-time acknowledgment.

Common follow-ups: Why has the Java community increasingly moved away from checked exceptions in newer API designs, like the Streams API?;What determines whether a new custom exception class should extend Exception or RuntimeException?

Error Handling;Java Fundamentals: Syntax Data Types & Operators

What is the correct usage of try-with-resources, and how does it automatically ensure a resource's close() method is called even if an exception occurs?

Intermediate
try-with-resources (any resource implementing AutoCloseable, declared within the try's parentheses) automatically calls the resource's close() method when the try block exits, whether normally or via an exception, eliminating the verbose and error-prone manual try-finally pattern previously required to guarantee resource cleanup -- multiple resources can be declared together (closed in reverse declaration order), and if both the try block's code AND the automatic close() call throw exceptions, the original exception is preserved as the primary one with the close()-triggered exception attached as a suppressed exception, accessible via getSuppressed().
// Modern try-with-resources: automatic, guaranteed cleanup
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
} catch (IOException e) {
    e.printStackTrace();
}
// reader.close() is called automatically, even if readLine() throws

// Old, verbose, error-prone equivalent
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
try {
    String line = reader.readLine();
} finally {
    reader.close();  // easy to forget, or to get wrong if this itself throws
}
Real-world example A file-processing method using try-with-resources for both a FileInputStream and a BufferedReader chained together guarantees both are properly closed in the correct reverse order even if reading throws an exception midway through, eliminating an entire category of resource leak bugs common with manual try-finally cleanup code.

Common follow-ups: What happens if the resource's close() method itself throws an exception while the try block also threw one -- which exception 'wins'?;What specific interface must a class implement to be usable in a try-with-resources statement?

I/O & NIO;Error Handling

How does exception chaining (via the cause mechanism, initCause() or a constructor accepting a Throwable) preserve the original root-cause exception when wrapping and rethrowing a different exception type, and why does this matter for debugging?

Advanced
When catching a lower-level exception and throwing a different, higher-level exception more meaningful to the caller (a common pattern for translating a checked exception into an unchecked one, or abstracting away an implementation detail), passing the original exception as the cause (via a constructor like new ServiceException("message", originalException)) preserves the complete original stack trace as part of the new exception's own stack trace output, chained via getCause() -- without this chaining, the original exception's information (exactly where and why the underlying failure occurred) would be lost entirely, replaced only by the wrapping exception's own, potentially much less specific, stack trace pointing only to the wrapping/rethrow location.
public void processOrder(Order order) {
    try {
        paymentGateway.charge(order);
    } catch (IOException e) {
        // Wraps and preserves the original cause -- getCause() on OrderProcessingException returns the IOException
        throw new OrderProcessingException("Failed to process order " + order.getId(), e);
    }
}
Real-world example A production incident investigation quickly identifies that a high-level OrderProcessingException was actually caused by a low-level network timeout deep in a payment gateway client, entirely because the original IOException was properly chained as the cause rather than discarded, preserving the full diagnostic stack trace chain in the logs.

Common follow-ups: What happens to the chained cause's information if you forget to pass it when constructing the wrapping exception?;How do logging frameworks typically render a full chain of caused-by exceptions in log output?

Error Handling;Logging in Java (java.util.logging SLF4J Log4j)

What is a common anti-pattern with catching exceptions too broadly (catch (Exception e) or worse, catch (Throwable t)), and why is it generally discouraged?

Intermediate
Catching an overly broad exception type (Exception, or especially Throwable, which also catches Errors like OutOfMemoryError that generally indicate unrecoverable JVM-level problems) risks silently swallowing exceptions the code has no genuine ability to meaningfully handle, masking real bugs (like an unexpected NullPointerException from a coding mistake being silently caught alongside an intentionally-handled, expected checked exception), making debugging significantly harder since the actual root cause of a problem gets hidden behind generic, non-specific catch-all handling -- best practice catches only the specific exception types the code can genuinely, meaningfully recover from, letting anything else propagate up to a level equipped to handle it (or a global handler) rather than being silently absorbed.
// Anti-pattern: overly broad catch swallows everything, including bugs
try {
    processOrder(order);
} catch (Exception e) {
    log.error("Something went wrong");  // swallows EVERYTHING, including NullPointerException bugs!
}

// Better: catch only the specific, genuinely-expected/recoverable exception
try {
    processOrder(order);
} catch (PaymentDeclinedException e) {
    notifyCustomerOfDeclinedPayment(order, e);
}
Real-world example A production bug where orders were silently failing without any visible error is eventually traced to a broad catch (Exception e) block that had been quietly swallowing an unrelated NullPointerException bug alongside the specific PaymentDeclinedException the code was actually intended to handle, masking the real defect for months.

Common follow-ups: Why is catching Throwable specifically considered even more dangerous than catching Exception?;What's an appropriate strategy for a truly generic, top-level 'catch anything and log it' handler (like a global exception handler) versus this specific anti-pattern?

Global Exception Handling & Middleware;Logging in Java (java.util.logging SLF4J Log4j)

How would you design a custom exception hierarchy for an application, deciding when to create new exception classes versus reusing existing ones, and what fields/context should a well-designed custom exception carry?

Advanced
A well-designed custom exception hierarchy typically has a common base exception for the application/domain (like OrderException extends RuntimeException), with specific subtypes for genuinely distinct failure categories callers might need to handle differently (InsufficientInventoryException, PaymentDeclinedException) -- new exception classes are warranted when calling code needs to programmatically distinguish and react differently to a specific failure category (catching the specific subtype), while reusing a generic exception (or a shared parent type) is appropriate when the specific failure reason doesn't actually change how calling code should react; a well-designed custom exception should carry sufficient contextual data (not just a message string) for both logging/debugging purposes and, where relevant, for calling code to programmatically inspect and react to the specific failure details.
public class InsufficientInventoryException extends OrderException {
    private final String productId;
    private final int requested, available;

    public InsufficientInventoryException(String productId, int requested, int available) {
        super(String.format("Insufficient inventory for %s: requested %d, available %d", productId, requested, available));
        this.productId = productId;
        this.requested = requested;
        this.available = available;
    }
    // getters let calling code programmatically inspect the specific shortfall, not just read a message string
}
Real-world example An order-processing system's InsufficientInventoryException carries structured fields (productId, requested, available quantities) rather than just an error message string, letting the calling UI code programmatically extract and display exactly how many units are actually available to the customer, rather than needing to parse an unstructured error message.

Common follow-ups: When does creating a new specific exception subtype provide genuine value versus becoming unnecessary proliferation?;How would you design this hierarchy to work well with a global exception handler mapping exceptions to appropriate HTTP status codes?

Error Handling;OOP & Classes

What is the finally block's execution guarantee, and what are the specific edge cases where a finally block might NOT execute?

Intermediate
A finally block is guaranteed to execute after the corresponding try (and any matching catch) block completes, whether execution left normally, via a caught exception, an uncaught exception propagating out, or even a return/break/continue statement inside the try/catch -- the notable exceptions to this guarantee are: the JVM itself terminating abruptly (System.exit(), a JVM crash, or the process being killed), an infinite loop or blocking call within the try block that never actually returns control, or a Thread.stop() call (a deprecated, dangerous, unsafe method) -- outside these edge cases, finally is an extremely reliable guarantee for cleanup code.
public int riskyMethod() {
    try {
        return 1;
    } finally {
        System.out.println("finally still runs even though try already returned!");
        // Note: a return statement HERE would actually override/discard the try block's return value --
        // a well-known finally pitfall, generally considered bad practice
    }
}
Real-world example A resource-cleanup routine placed in a finally block reliably executes even when the corresponding try block's code throws an unexpected exception or returns early, EXCEPT in the rare case where the entire JVM process is forcibly terminated via System.exit() elsewhere in the code, an important edge case to be aware of for critical cleanup logic.

Common follow-ups: Why is returning a value from within a finally block considered a serious anti-pattern, and what confusing behavior does it cause?;How does try-with-resources' automatic close() interact with a finally block if both are present?

I/O & NIO;Error Handling

How does exception handling interact with performance, particularly regarding the cost of filling in a stack trace, and how would you optimize exception usage in a performance-critical hot path?

Advanced
Constructing an exception (specifically, the fillInStackTrace() call invoked by Throwable's constructor, which captures the entire current call stack) has a genuinely measurable performance cost, disproportionately significant if exceptions are being thrown routinely as part of normal, expected control flow in a performance-critical hot path (an anti-pattern in itself -- exceptions should represent exceptional conditions, not routine control flow) -- for cases where you genuinely need lightweight, frequently-thrown exceptions (like in a high-throughput parser signaling routine, expected parse failures), overriding fillInStackTrace() to skip stack trace capture entirely (returning `this` without calling super) can eliminate this overhead, at the obvious cost of losing stack trace information for debugging that specific exception type.
// A lightweight exception for a high-frequency, expected control-flow scenario
public class FastValidationException extends RuntimeException {
    public FastValidationException(String message) {
        super(message, null, false, false);  // disables both suppression AND stack trace capture
    }
}

// vs. the default, which captures a full stack trace on every single construction --
// expensive if thrown thousands of times per second in a hot path
Real-world example A high-throughput data validation pipeline processing millions of records per minute switches its routine, expected-to-occur-frequently ValidationException to disable stack trace capture (using the four-argument Throwable constructor), measurably improving throughput since stack trace capture had become a significant fraction of total processing time at that volume.

Common follow-ups: Why is throwing exceptions for routine, expected control flow generally considered an anti-pattern in the first place, separate from the pure performance cost?;What's lost in debuggability by disabling stack trace capture, and how would you mitigate that trade-off?

Diagnostics & Performance;Design Patterns in Java

What is the multi-catch syntax (catch (IOException | SQLException e)) introduced in Java 7, and what restriction applies to the exception types combined in a single multi-catch block?

Intermediate
Multi-catch lets a single catch block handle multiple, unrelated exception types with identical handling logic, avoiding duplicated catch blocks with the exact same body for each type -- the restriction is that the combined exception types must not be related by a subclass/superclass relationship (catching both a specific exception AND its own supertype together in the same multi-catch is redundant and rejected by the compiler), and the resulting caught variable is implicitly treated as the most specific common supertype of the listed types (and effectively final, so it can't be reassigned within the catch block).
try {
    riskyIOOperation();
    riskyDatabaseOperation();
} catch (IOException | SQLException e) {
    // shared handling logic for both, avoiding duplicated catch blocks
    log.error("Operation failed", e);
    throw new ServiceException("Operation failed", e);
}
// e's static type here is Exception (the nearest common supertype of IOException and SQLException)
Real-world example A method that can fail with either an IOException or a SQLException, but wants identical error-logging-and-wrapping behavior for both, uses multi-catch to consolidate what would otherwise be two nearly-identical catch blocks into one, reducing code duplication while still only catching the specific types actually expected.

Common follow-ups: Why does the compiler reject combining a supertype and its own subtype together in a multi-catch?;How does the caught variable's static type get determined when the combined exception types don't share an obvious common ancestor beyond Exception?

Error Handling;Java Fundamentals: Syntax Data Types & Operators

How would you implement a custom checked exception that requires additional context to be attached as the exception propagates up multiple layers of a call stack, without losing information at each layer?

Advanced
A common pattern for preserving context across multiple call stack layers involves each layer catching the exception, adding its own relevant contextual information (like which specific record ID was being processed when the failure occurred), and rethrowing either the same exception (after mutating/adding to accumulated context, if the exception type supports that) or a new wrapping exception with the original preserved as the cause -- accumulating context this way at each layer (rather than only having the innermost, most technical error message) produces a much more actionable end-to-end error trail when the exception is eventually logged or displayed, letting an on-call engineer understand not just what technically failed but the full business-level context of what the system was attempting to do when it failed.
public class BatchProcessingException extends Exception {
    private final List<String> processingContext = new ArrayList<>();

    public BatchProcessingException(String message, Throwable cause) { super(message, cause); }

    public BatchProcessingException addContext(String context) {
        processingContext.add(context);
        return this;
    }
}

// Layer 1 (innermost): throws with initial context
// Layer 2: catches, adds "processing batch #47" context, rethrows the SAME exception instance
// Layer 3: catches, adds "nightly reconciliation job" context, logs full accumulated context chain
Real-world example A nightly batch reconciliation job's failure log entry shows not just the low-level technical error (a specific database constraint violation) but also the full accumulated business context ("processing batch #47, record #1523, nightly reconciliation job run at 2am"), added incrementally as the exception propagated up through each processing layer, dramatically speeding up the on-call engineer's root-cause diagnosis compared to a bare technical error message alone.

Common follow-ups: What's the trade-off of mutating and rethrowing the same exception instance versus wrapping in a new exception at each layer?;How do structured logging frameworks (like using MDC - Mapped Diagnostic Context) provide an alternative way to accumulate this same contextual information?

Logging in Java (java.util.logging SLF4J Log4j);Diagnostics & Performance

What is the difference between Error and Exception in Java's Throwable hierarchy, and why should application code generally avoid catching Error subclasses like OutOfMemoryError or StackOverflowError?

Intermediate
Both Error and Exception extend the common Throwable superclass, but Error is specifically reserved for serious, generally unrecoverable conditions typically caused by the JVM/environment itself running out of critical resources or hitting a fundamental limit (OutOfMemoryError, StackOverflowError, LinkageError) rather than an application-level failure condition; catching these is generally discouraged (though technically possible, since they're not enforced as checked) because the JVM's own internal state may already be significantly compromised by the time such an Error is thrown (for example, after an OutOfMemoryError, there may not be enough free memory remaining to even execute a meaningful recovery/cleanup routine), making genuine recovery unreliable at best and potentially masking a severe underlying problem that should instead cause the application to fail fast and restart cleanly.
try {
    performRecursiveOperation();
} catch (StackOverflowError e) {  // technically legal, but generally a bad idea
    log.error("Stack overflow occurred", e);
    // The JVM's stack state at this point may be in a precarious condition;
    // "recovering" and continuing normal operation is often unreliable
}
Real-world example A team debugging a service that behaves erratically after briefly catching and 'handling' an OutOfMemoryError (attempting to continue normal operation rather than restarting) discovers the JVM's internal state was left in a subtly corrupted condition by the near-exhaustion event, leading them to instead let such Errors propagate uncaught and rely on process supervision (like Kubernetes) to restart the instance cleanly.

Common follow-ups: Are there any legitimate, narrow scenarios where catching a specific Error subclass is genuinely appropriate?;How does this distinction between Error and Exception inform designing a global/top-level exception handler's scope?

JVM JRE & Memory;Garbage Collection

Showing 1–10 of 15