---
description: Java Error Handling
alwaysApply: false
---

# Java Error Handling

Exceptions for exceptional conditions. Validation at boundaries. Never swallow errors.

## Exception Hierarchy

```java
// Unchecked application base
public abstract class ApplicationException extends RuntimeException {
    private final String errorCode;
    protected ApplicationException(String code, String msg) { super(msg); this.errorCode = code; }
    protected ApplicationException(String code, String msg, Throwable cause) { super(msg, cause); this.errorCode = code; }
}
public class NotFoundException extends ApplicationException {
    public NotFoundException(String entity, Object id) {
        super("NOT_FOUND", "%s with id '%s' not found".formatted(entity, id));
    }
}
```

## Checked vs Unchecked

- **Checked**: recoverable conditions at system boundaries (IOException, SQLException)
- **Unchecked**: programming errors and business rule violations (IllegalArgumentException, NotFoundException)
- Rule: caller CAN and SHOULD handle → checked; bug or unrecoverable → unchecked

## Guard Clauses

```java
Objects.requireNonNull(customerId, "customerId must not be null");
if (items.isEmpty()) throw new IllegalArgumentException("Order must have items");
```

## Result Pattern (Alternative)

```java
public sealed interface Result<T> permits Result.Success, Result.Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String code, String message) implements Result<T> {}
}
// Use for expected business failures (validation, conflicts)
```

## Logging Exceptions

```java
// Log with business context, exception as last parameter for stack trace
log.error("Payment failed orderId={} amount={}: {}", orderId, amount, ex.getMessage(), ex);
// Don't log AND throw at the same level — choose one
```

## Validation

```java
public record CreateUserRequest(
    @NotBlank @Size(min = 2, max = 200) String name,
    @NotBlank @Email String email
) {}
```

## Anti-Patterns

- **Never** catch `Exception`/`Throwable` broadly — catches NPE, InterruptedException, everything
- **Never** empty catch blocks — at minimum `log.debug`
- **Never** wrap without context: `throw new RuntimeException(e)` — add what failed
