Built-in exception types (IOException, NullPointerException) cover system-level and general errors but cannot capture domain-specific failures. A banking application needs InsufficientFundsException, not a generic IllegalArgumentException. Without custom exceptions, error handling becomes vague and loses business context.
Custom exceptions are user-defined classes that extend Exception (for checked) or RuntimeException (for unchecked). They carry domain-specific information — error codes, field names, additional context — enabling precise error handling and meaningful error messages.
A custom exception class extends Exception or RuntimeException, provides constructors, and optionally adds custom fields and methods. When thrown, it behaves like any other exception — it can be caught by type, propagated via throws, and chained to other exceptions.
digraph java_custom_exceptions {
rankdir=TB
node [shape=box style=filled fillcolor="#f0f4ff" fontname="Helvetica" fontsize=12]
edge [fontname="Helvetica" fontsize=10]
Base [label="Exception (or RuntimeException)" fillcolor="#ffe5cc"]
Custom [label="InsufficientFundsException\n- double balance\n- double requested\n- String accountId"]
Throw [label='throw new\nInsufficientFundsException(bal, req)']
Catch [label="catch (InsufficientFundsException e)\ne.getBalance()\ne.getRequested()" fillcolor="#d4edda"]
Custom -> Base [label="extends"]
Throw -> Custom
Catch -> Throw [label="caught by type"]
}- Checked vs unchecked choice: Extend
Exceptionfor recoverable business errors;RuntimeExceptionfor programming mistakes - Constructors: Typically provide no-arg, message, cause, and all-combined constructors
- Custom fields: Add domain data (error codes, entity IDs) for richer handling
- Serializable: Exception implements Serializable — custom fields should be serializable too
- Built from: Java Exception Hierarchy — custom exceptions extend Exception or RuntimeException
- Built from: Throw and Throws — custom exceptions are thrown with
throw - Builds into: Java JDBC — database layers define SQLException subtypes or wrap them in custom exceptions
- Related: Try-Catch-Finally — custom exceptions are caught like any other
- Serialization UID: Always define
serialVersionUIDto avoid InvalidClassException across versions - Too many exception types: Proliferating custom exceptions creates maintenance burden
- Wrapper exceptions: Throwing custom exception wrapping the original cause preserves the stack trace
- Checked exception fatigue: Overusing checked custom exceptions makes APIs painful to use