Java Custom and Multiple Exceptions Explained with Examples

Custom exceptions express a failure in the vocabulary of your application.

Lesson 67 of 127Intermediate13 min read

Custom exceptions express a failure in the vocabulary of your application. A reservation method can report insufficient stock rather than exposing an unrelated parser or storage message. Multi-catch lets one handler address several unrelated exception types when they genuinely share a response. These tools serve different purposes: custom types clarify a domain contract, while catch structure determines recovery. Choose them only when they improve the caller's understanding and preserve the information needed to diagnose the original failure.

Define a custom type with a meaningful contract

A custom exception is an ordinary class extending an appropriate throwable type. Extending Exception creates a checked type unless you extend an unchecked branch; extending RuntimeException creates an unchecked type. Include constructors that convey useful details and accept a cause when translation is needed. A class named ProblemException adds little meaning. InsufficientStockException explains a specific rejected operation and gives callers a type they can handle separately from unexpected failures.

Choose checked or unchecked deliberately

A checked domain exception asks callers to acknowledge an anticipated outcome through catch or throws. An unchecked argument exception can reject inputs that violate a method's precondition. Neither choice proves severity or guarantees good recovery. Follow the surrounding API's design and the application's actual caller responsibilities. Avoid creating a checked exception solely to force catches everywhere, or making everything unchecked solely to reduce the number of declarations.

Preserve useful details rather than parsing messages

Structured fields such as requested and available let code respond without extracting numbers from an English sentence. The message remains useful for people, while accessors support programmatic handling. Keep those details immutable where practical. Do not attach secrets or unnecessary personal information to exceptions that may be logged. A custom type should make the failure more informative, not become a container for every piece of application state.

Use multi-catch for a shared response

A catch parameter can list alternatives separated by a vertical bar. IOException and NumberFormatException can share a handler when either means a calibration reading cannot be obtained from a file. The alternatives cannot be related by inheritance, because a superclass already covers its subtype. A multi-catch parameter cannot be reassigned. Group failures only when the recovery policy really is shared; separate them when the user needs different corrective instructions.

Order distinct catches from specific to broad

If you use multiple catch clauses instead of multi-catch, place narrower types before their broader superclass. A preceding Exception catch would consume later Exception-subclass cases and make them unreachable. Specific handling can recover from an expected domain rejection while allowing unexpected failures to propagate. Do not add a final broad catch that announces success. The catch structure should reflect which failures are understood and which should still be reported as failed operations.

Translate only at a useful abstraction boundary

A repository might translate a low-level file failure into a catalog-loading exception, retaining the original cause. The service caller then sees a meaningful domain operation while diagnostics keep the underlying stack. Do not wrap the same exception repeatedly at every method without adding context. Choose the boundary that changes responsibility. Follow exception hierarchy to distinguish the resulting cause chain from the new exception's inheritance relationship.

Practice tasks and checks

Add a requested accessor to InsufficientStockException and verify both structured fields in a test. Add separate argument validation for negative counts so a malformed request is not mistaken for a shortage. For the tag example, define and test the null policy. For CalibrationMultiCatch, change the content to a valid integer and predict the normal output, then exercise a missing-file read under the same protected boundary. Decide whether those two failures deserve one response in your application or separate correction messages. Finally, write a custom wrapper constructor accepting a Throwable cause and verify the original exception remains reachable. Each custom type or catch group should have a stated purpose rather than merely adding more exception names.

Java Summary

  • Custom exception types should clarify a real domain outcome.
  • Multi-catch alternatives cannot be related by inheritance.
  • Preserve structured details and causes where callers need them.
  • Continue with resource-failure handling when these examples are clear.

Java Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Expose a checked stock rejection

Save this program as CustomStockFailure.java. Compile it with javac CustomStockFailure.java and run it with java CustomStockFailure.

Expected output:

The reservation exceeds available stock, so the method raises its declared checked type. The constructor creates a human-readable message and stores available separately. The catch can display the message and obtain three without parsing text. This method assumes nonnegative counts; add argument validation before the stock comparison when generalizing it. No inventory mutation occurs, so rejecting the request cannot partially reduce stock.

Java
public class CustomStockFailure {
    static class InsufficientStockException extends Exception {
        private final int available;
        InsufficientStockException(int requested, int available) {
            super("Requested " + requested + ", available " + available);
            this.available = available;
        }
        int available() { return available; }
    }
    static void reserve(int requested, int available) throws InsufficientStockException {
        if (requested > available) throw new InsufficientStockException(requested, available);
    }
    public static void main(String[] args) {
        try {
            reserve(5, 3);
        } catch (InsufficientStockException error) {
            System.out.println(error.getMessage());
            System.out.println(error.available());
        }
    }
}
Output
Requested 5, available 3
3
Example 2

Use an unchecked argument-specific subtype

Save this program as CustomTagFailure.java. Compile it with javac CustomTagFailure.java and run it with java CustomTagFailure.

Expected output:

The custom class extends an unchecked argument-error family. requireTag needs no checked throws declaration, and the superclass catch still matches the custom instance. Printing its actual class distinguishes the domain-specific failure from the broader handler type. The contract assumes non-null text; decide whether null should produce the same custom rejection rather than accidentally letting isBlank throw an unrelated exception.

Java
public class CustomTagFailure {
    static class InvalidTagException extends IllegalArgumentException {
        InvalidTagException(String message) { super(message); }
    }
    static String requireTag(String tag) {
        if (tag.isBlank()) throw new InvalidTagException("Tag is blank");
        return tag;
    }
    public static void main(String[] args) {
        try {
            requireTag(" ");
        } catch (IllegalArgumentException error) {
            System.out.println(error.getClass().getSimpleName());
        }
    }
}
Output
InvalidTagException
Example 3

Share one response for file and parse failures

Save this program as CalibrationMultiCatch.java. Compile it with javac CalibrationMultiCatch.java and run it with java CalibrationMultiCatch.

Expected output:

The file read succeeds but parsing its content fails, selecting the multi-catch through NumberFormatException. An I/O failure inside the same protected expression would select the same handler through the other alternative. These types are unrelated by inheritance, satisfying multi-catch rules. The handler reports a common unavailable-reading outcome while preserving the actual type for this demonstration. File creation, writing, and cleanup have their own checked obligations outside the inner catch.

Java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CalibrationMultiCatch {
    public static void main(String[] args) throws IOException {
        Path file = Files.createTempFile("calibration-", ".txt");
        try {
            Files.writeString(file, "not-a-number");
            try {
                System.out.println(Integer.parseInt(Files.readString(file)));
            } catch (IOException | NumberFormatException error) {
                System.out.println("Calibration reading unavailable");
                System.out.println(error.getClass().getSimpleName());
            }
        } finally {
            Files.deleteIfExists(file);
        }
    }
}
Output
Calibration reading unavailable
NumberFormatException

Java FAQ

Can I multi-catch Exception and IOException together?

No. IOException is already an Exception subtype, so the alternatives overlap by inheritance. Catch the broader type only when its broader handling policy is actually appropriate, or use separate meaningful boundaries.

Should every application failure have a new class?

No. Standard types often express the contract well. Introduce a custom type when callers need to distinguish a domain outcome or obtain structured details beyond a generic failure category.

What should a custom exception class contain?

At minimum, a constructor taking a message and one taking a message and a cause, both passing their arguments to super. Add fields for structured details callers need, such as the requested and available quantities, with getters, and keep the class small and focused on one kind of failure.

How should I name a custom exception?

End the name with Exception and describe what went wrong in your application's language: InsufficientStockException, PaymentDeclinedException, InvalidBookingDateException. A name such as MyException or BadThingException tells the caller nothing useful.

Can I reassign the variable in a multi-catch block?

No. In catch (IOException | NumberFormatException e), the variable e is implicitly final, and its type is the closest common supertype of the alternatives, here Exception. You can still read its message, log it or wrap it in another exception.

Java Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

Why store available stock as a field in the exception?

Multiple choicemedium

Which parent produces an unchecked custom argument exception?

Java Quiz

2 questions to check what you learned.

  1. Why are IOException and NumberFormatException valid multi-catch alternatives?

    • Neither is a subtype of the other
    • Both are always checked
    • They have identical class names
  2. What should a translated exception retain?

    • The original cause when useful
    • Only the method filename
    • No contextual information