JavaError HandlingIntermediate

Custom Exceptions

Custom exceptions are user-defined exception classes that allow developers to create specific error types relevant to their application's domain logic.

Review the syntaxStudy the examplesOpen the coding app
class MyCustomException extends Exception { ... }
class MyRuntimeException extends RuntimeException { ... }

This static page keeps the syntax and examples indexed for search, while the coding app handles interactive exploration and saved references.

What it does

Overview

Custom exceptions are user-defined exception classes that allow developers to create specific error types relevant to their application's domain logic.

While Java provides a rich set of built-in exception classes, creating custom exceptions is essential for representing application-specific error conditions. This improves code clarity, makes error handling more precise, and allows for more granular control over exception propagation. Custom exceptions are created by extending either `java.lang.Exception` (for checked exceptions) or `java.lang.RuntimeException` (for unchecked exceptions). Typically, custom exception classes include constructors that call the superclass constructor, often accepting a message string and sometimes a `cause` (another `Throwable`) for exception chaining. When to choose checked vs. unchecked depends on whether the caller is expected to recover from the error: checked for recoverable errors, unchecked for programming errors or unrecoverable situations. Best practices include giving custom exceptions meaningful names, providing clear error messages, and considering adding fields to hold additional context-specific information relevant to the error condition (e.g., an error code, invalid input value).

Quick reference

Syntax

class MyCustomException extends Exception { ... }
class MyRuntimeException extends RuntimeException { ... }

See it in practice

Examples

1

Defining and throwing a custom checked exception

class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
    public InsufficientFundsException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Insufficient funds for withdrawal. Available: " + balance + ", Requested: " + amount);
        }
        balance -= amount;
        System.out.println("Withdrawal successful. New balance: " + balance);
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);
        try {
            account.withdraw(500);
            account.withdraw(700); // This will throw the custom exception
        } catch (InsufficientFundsException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}
Output:
Withdrawal successful. New balance: 500.0 Error: Insufficient funds for withdrawal. Available: 500.0, Requested: 700.0

A `InsufficientFundsException` is defined by extending `Exception`, making it a checked exception. The `withdraw` method declares and throws it when funds are insufficient.

2

Defining and throwing a custom unchecked exception

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

public class DataProcessor {
    public int parsePositiveInt(String input) {
        try {
            int value = Integer.parseInt(input);
            if (value <= 0) {
                throw new InvalidInputException("Input must be a positive integer: " + input);
            }
            return value;
        } catch (NumberFormatException e) {
            throw new InvalidInputException("Invalid number format: " + input, e); // Chaining exceptions
        }
    }

    public static void main(String[] args) {
        DataProcessor processor = new DataProcessor();
        System.out.println("Parsed: " + processor.parsePositiveInt("123"));
        try {
            processor.parsePositiveInt("abc");
        } catch (InvalidInputException e) {
            System.err.println("Error: " + e.getMessage());
            System.err.println("Cause: " + e.getCause().getMessage());
        }
        try {
            processor.parsePositiveInt("-10");
        } catch (InvalidInputException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}
Output:
Parsed: 123 Error: Invalid number format: abc Cause: For input string: "abc" Error: Input must be a positive integer: -10

An `InvalidInputException` extends `RuntimeException`, making it unchecked. It's thrown for invalid input and also demonstrates exception chaining by wrapping a `NumberFormatException`.

3

Custom exception with additional data

class ProductNotFoundException extends Exception {
    private final String productId;

    public ProductNotFoundException(String message, String productId) {
        super(message);
        this.productId = productId;
    }

    public String getProductId() {
        return productId;
    }
}

public class ProductService {
    public String findProduct(String id) throws ProductNotFoundException {
        if (!id.equals("P123")) {
            throw new ProductNotFoundException("Product not found", id);
        }
        return "Found Product: " + id;
    }

    public static void main(String[] args) {
        ProductService service = new ProductService();
        try {
            System.out.println(service.findProduct("P123"));
            service.findProduct("P456");
        } catch (ProductNotFoundException e) {
            System.err.println(e.getMessage() + ". ID: " + e.getProductId());
        }
    }
}
Output:
Found Product: P123 Product not found. ID: P456

The `ProductNotFoundException` includes a `productId` field to provide more context about the error, accessible via a getter.

Debug faster

Common Errors

1

Design Flaw

Cause: Creating a custom checked exception for an error that callers cannot reasonably recover from.

Fix: If an error indicates a programming bug or an unrecoverable system failure, it's better to extend `RuntimeException` to make it an unchecked exception. This avoids forcing callers to catch errors they can't fix.

// If this is truly unrecoverable by calling code, it should be RuntimeException
// class CriticalSystemFailureException extends Exception { /* ... */ }
2

Missing Constructors

Cause: A custom exception class lacks standard constructors (e.g., `(String message)`, `(String message, Throwable cause)`).

Fix: Always provide at least the `(String message)` constructor, and ideally `(String message, Throwable cause)` for proper exception chaining, by calling `super()`.

class MyException extends Exception {
    // Compile-time error if no default constructor in superclass and no explicit constructor here
    // (empty class)
}

Runtime support

Compatibility

JVMN/AJava 1.0+

Source: Oracle Java Documentation

Common questions

Frequently Asked Questions

Custom exceptions are user-defined exception classes that allow developers to create specific error types relevant to their application's domain logic.

Design Flaw: If an error indicates a programming bug or an unrecoverable system failure, it's better to extend `RuntimeException` to make it an unchecked exception. This avoids forcing callers to catch errors they can't fix. Missing Constructors: Always provide at least the `(String message)` constructor, and ideally `(String message, Throwable cause)` for proper exception chaining, by calling `super()`.