Custom Exceptions in Java

A custom exception is a subclass of Exception or RuntimeException that carries domain-specific information. Create one when a built-in type (IllegalArgumentException, IllegalStateException) doesn't carry enough context, or when callers need to catch a specific type distinctly.

Minimal unchecked exception

public class OrderNotFoundException extends RuntimeException {
    private final long orderId;

    public OrderNotFoundException(long orderId) {
        super("Order not found: " + orderId);
        this.orderId = orderId;
    }
    public long orderId() { return orderId; }
}

Standard constructors

Good custom exceptions follow the Throwable four-constructor convention so callers can always chain a cause:

public class OrderNotFoundException extends RuntimeException {
    public OrderNotFoundException()                               { super(); }
    public OrderNotFoundException(String message)                 { super(message); }
    public OrderNotFoundException(String message, Throwable cause){ super(message, cause); }
    public OrderNotFoundException(Throwable cause)                 { super(cause); }
}

Checked or unchecked?

Use checked (extends Exception) when…Use unchecked (extends RuntimeException) when…
The caller has a realistic recovery path.The error signals a programming bug or unrecoverable state.
You're writing a library whose consumers should be forced to decide.The exception is a "business" error used in large swaths of code (Spring, JPA do this).

Modern Java trends toward unchecked exceptions — they compose better with lambdas and keep method signatures clean.

Carrying context

public class ValidationException extends RuntimeException {
    private final List<String> errors;
    public ValidationException(List<String> errors) {
        super("Validation failed: " + String.join(", ", errors));
        this.errors = List.copyOf(errors);
    }
    public List<String> errors() { return errors; }
}

Sealed exception hierarchies (Java 17+)

public sealed class ServiceException extends RuntimeException
        permits NotFoundException, ConflictException, UnauthorizedException {
    protected ServiceException(String m) { super(m); }
}
public final class NotFoundException  extends ServiceException { ... }
public final class ConflictException  extends ServiceException { ... }

Common mistakes

  • Exception per call site — ten near-identical classes instead of one with a field.
  • Dropping the cause — always accept a Throwable cause constructor.
  • Extending Throwable directly — use Exception or RuntimeException.
  • Putting business logic in the exception — the class is a messenger, not a service.

Related

Pillar: Java exceptions. Siblings: throw, checked vs unchecked.