skip to content

What are the conventional constructors a custom exception class should provide, and how do they delegate to super?

level: middleimportance: must knowfreq 72%

answer

  1. Four constructors: (), (String), (String, Throwable), (Throwable)
  2. Mirror Throwable; constructors aren't inherited
  3. Each body = a single super(...) call
  4. Cause-bearing constructors enable exception chaining
  5. getMessage()/getCause() read what super stored

basics

~10 s

Provide four constructors mirroring Throwable: no-arg, message-only (String), message-and-cause (String, Throwable), and cause-only (Throwable). Each just calls the matching super(...) so the message and cause are stored correctly.

solid answer

~50 s

By convention a custom exception offers the same four constructors as Throwable so it slots cleanly into existing tooling and wrapping patterns: a no-arg constructor, a (String message) constructor, a (String message, Throwable cause) constructor, and a (Throwable cause) constructor. Each delegates to the corresponding super(...) call, because Throwable already implements the storage and retrieval of the detail message (getMessage()) and the underlying cause (getCause()). You almost never re-implement that logic; you just forward arguments. The message-and-cause and cause-only forms are essential for exception chaining — when you catch a low-level exception and rethrow a domain-specific one, you pass the original as the cause so the stack trace and root cause are preserved. If your exception needs extra context (an error code, an entity id), add a field plus a constructor that takes it and still forwards message/cause to super.

code

java · 13 lines
java
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); }
}

// Usage with chaining:
try {
    repository.find(id);
} catch (SQLException e) {
    throw new OrderNotFoundException("No order " + id, e);
}

go deeper

for a junior

Knows to call super(message) and that there's a no-arg and a message constructor.

for a middle

Can list and write all four Throwable-mirroring constructors and explain that each just delegates to super.

for a senior

Explains why constructors aren't inherited, why chaining via the cause matters, and how to add custom-state constructors while still forwarding message/cause.

for a principal

Standardizes a base exception (with error codes/context) and chaining conventions across services so traces remain debuggable and contracts consistent.

## What constructors are we talking about? `Throwable` — the root of all things you can throw — defines four public constructors. Because `Exception` and `RuntimeException` inherit them, the standard convention is for a custom exception to **mirror those four**: 1. `MyException()` — no arguments. 2. `MyException(String message)` — a human-readable **detail message**. 3. `MyException(String message, Throwable cause)` — a message **plus** the underlying exception that triggered this one. 4. `MyException(Throwable cause)` — just the underlying cause (its message becomes the detail message via `cause.toString()`). ## Why mirror all four? Constructors are **not inherited** in Java. If your class declares no constructors, it only gets the implicit no-arg one (and only if the superclass has an accessible no-arg constructor). So to let callers create your exception with a message, or with a cause, you must **explicitly declare** those constructors. Mirroring `Throwable` means your exception works everywhere the JDK and libraries expect those shapes — particularly utilities and frameworks that wrap one exception in another. ## What each constructor delegates to Each constructor's body is typically a single `super(...)` call passing the arguments straight through: ```java 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); } } ``` You delegate because `Throwable` already stores and exposes this state: - **detail message** — saved by `super(message)`, read back with `getMessage()` (and shown in the stack trace). - **cause** — saved by `super(message, cause)` or `super(cause)`, read back with `getCause()`. Reimplementing this would be redundant and error-prone, so you simply forward. ## The cause and exception chaining The **cause** is the original exception that led to the new one. Setting it is called **exception chaining** (or *wrapping*). The pattern: catch a low-level, often checked or implementation-specific exception, and rethrow a higher-level, domain-meaningful one — but pass the original as the cause so nothing is lost: ```java try { repository.find(id); } catch (SQLException e) { throw new OrderNotFoundException("No order " + id, e); // e is the cause } ``` When printed, the stack trace shows the new exception and then `Caused by: java.sql.SQLException: ...`, preserving the root cause for debugging. This is exactly why the `(String, Throwable)` and `(Throwable)` constructors matter — without them you would lose the original failure or have to call `initCause()` separately. > Note: if you don't pass a cause, you can still attach one later with `initCause(Throwable)`, but only once and only if a cause wasn't already set. Passing it through the constructor is cleaner. ## Adding custom state If your exception carries extra data (e.g. an `errorCode` or the offending `entityId`), add a final field and a constructor that accepts it, while still forwarding the message/cause to `super`: ```java public OrderNotFoundException(long orderId, Throwable cause) { super("No order " + orderId, cause); this.orderId = orderId; } ``` ## Summary of the convention - Provide the four `Throwable`-mirroring constructors (no-arg, message, message+cause, cause). - Each body is just `super(...)` — let `Throwable` store/expose the message and cause. - The cause-bearing forms enable **exception chaining**, preserving the root cause. - Add fields + extra constructors only for genuine extra context, still delegating message/cause to super.

  • Why is the (String message, Throwable cause) constructor important?
    It enables exception chaining: when you catch a low-level exception and rethrow a domain one, you pass the original as the cause, so getCause() and the 'Caused by:' stack-trace section preserve the root cause for debugging.
  • If you only need a message, can you skip the other constructors?
    You can, but it's discouraged. Omitting the cause-bearing constructors prevents clean chaining and breaks the convention libraries expect; providing all four is cheap and future-proof.

saying these in an interview costs you the question

  • Forgetting constructors are not inherited, so you must declare them explicitly
  • Re-storing message/cause in your own fields instead of delegating to super
  • Wrapping a low-level exception without passing it as the cause (losing the root cause)
  • Thinking the (Throwable) form needs a message — it derives one from cause.toString()

context