Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java exceptions: recovery boundaries and failure context

Last updated: 30 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

An exception transfers control from a failing operation to a matching handler while unwinding calls that do not handle it.

Catch what the boundary can handle

A parsing boundary can reject malformed input. It usually cannot repair an unrelated programming error such as an invalid internal array index. Catching Exception around an entire request obscures that difference.

Checked exceptions participate in compile-time handling or declaration requirements. RuntimeException subclasses are unchecked. Neither category proves that a failure is recoverable; recovery depends on what the current layer can actually do.

The parser translates a NumberFormatException into an IllegalArgumentException with the rejected field context and retains the original cause. That preserves useful diagnostic information without exposing the low-level parser as the public contract.

Do not turn a failure into a plausible value

Returning zero after an invalid quantity can make a bad order look like a valid empty order. Reject it explicitly, or return a structured validation result when callers need to report several field errors together.

The program catches the expected input rejection at a small command boundary and prints a user-facing message. The message names the field but avoids logging the raw input. Sensitive request values should not be copied into every exception and log line.

Exceptions from cleanup also matter. Try-with-resources keeps the primary failure and records relevant close failures as suppressed exceptions. A plain finally block can accidentally replace the original failure with its own.

Working program

Java
public class QuantityValidation {
    static int parseQuantity(String rawQuantity) {
        final int quantity;
        try {
            quantity = Integer.parseInt(rawQuantity);
        } catch (NumberFormatException invalidNumber) {
            throw new IllegalArgumentException("Quantity must be a whole number", invalidNumber);
        }
        if (quantity < 1 || quantity > 500) {
            throw new IllegalArgumentException("Quantity must be between 1 and 500");
        }
        return quantity;
    }
    public static void main(String[] args) {
        System.out.println(parseQuantity("12"));
        try {
            parseQuantity("many");
        } catch (IllegalArgumentException rejectedInput) {
            System.out.println(rejectedInput.getMessage());
            System.out.println(rejectedInput.getCause().getClass().getSimpleName());
        }
    }
}

Output

Output
12
Quantity must be a whole number
NumberFormatException

Cost and design choices

Parsing work depends on input length. Throwing an exception generally constructs diagnostic state and unwinds the stack; it is a poor default mechanism for a high-volume branch that is expected to fail routinely.

A bounded parser should reject enormous input before attempting expensive processing. An int parser still has a numeric range limit even if the business rule’s maximum is smaller.

Retries belong to a boundary that understands whether repeating the operation is safe. An exception alone does not prove idempotency, nor does it tell a caller whether a remote side effect already occurred.

Common Mistakes

  • Do not swallow an exception and continue with a success-shaped value.
  • Do not lose the original cause when translating a failure.
  • Do not catch Throwable as routine input handling.
  • Do not retry an external operation without considering repeated side effects.

Connect the contracts

Compare the boundary explained in Expected absence with the assumptions made by this program.

Compare the boundary explained in Failures in tasks with the assumptions made by this program.

Apply this contract in Spring

Spring MVC ProblemDetail: stable errors without leaking internals. These lessons keep framework assembly separate from the Java contract.

Continue with Spring delivery contracts

Continue with Spring retry and stale-write checks: choose the right HTTP contract.

java
exceptions
Storage details