Throwing Exceptions in Java: Checked vs Unchecked Exceptions and When to Use Each

Checked exceptions should be used only when the caller can realistically recover; unchecked exceptions fit programming errors and violated assumptions. Java gives developers both tools, but poor use of either one can turn clean code into a noisy mess. A team reading an API should be able to tell whether an error is part of normal operation or a sign that something is broken. That is the real purpose of choosing between checked and unchecked exceptions.

What Java Means by Checked and Unchecked Exceptions

A checked exception is an exception the compiler checks. If a method throws one, the method must either catch it or declare it with throws. These exceptions usually extend Exception, but not RuntimeException.

public void readConfig() throws IOException {
    Files.readString(Path.of("app.properties"));
}

An unchecked exception does not need to be caught or declared. It usually extends RuntimeException. Common examples include NullPointerException, IllegalArgumentException, IllegalStateException, and IndexOutOfBoundsException.

public void setDiscount(int percent) {
    if (percent < 0 || percent > 100) {
        throw new IllegalArgumentException("discount must be between 0 and 100");
    }
}

There is also Error, such as OutOfMemoryError. Application code should almost never throw or catch those. They signal serious JVM or system-level failures.

When Checked Exceptions Make Sense

Checked exceptions are useful when failure is expected, external, and recoverable. The caller can do something meaningful. That action may be retrying, asking for another file, showing a friendly message, or switching to a backup service.

Good cases for checked exceptions include:

  • File and network operations: A file may be missing. A socket may close.
  • Database access: A connection may fail or a transaction may time out.
  • Third-party service calls: A payment provider may reject a request or fail to respond.
  • Business operations with recoverable outcomes: A booking may fail because a room is no longer available.

Checked exceptions act like part of the method contract. They tell the caller, “This can happen, and the caller should be ready.” That is valuable for public APIs, libraries, and integration-heavy systems.

The problem starts when every minor issue becomes checked. It drives teams crazy when a simple method call requires three catch blocks that only wrap and rethrow the same thing. That noise adds little value. It also makes refactoring slower than it should be.

When Unchecked Exceptions Are the Better Choice

Unchecked exceptions work best when the failure means the code is being used incorrectly or the application state is broken. In those cases, forcing every caller to catch the exception is pointless. The correct fix is usually to change the code, not recover at runtime.

Common uses include:

  • Invalid arguments: A method receives a negative age, a null ID, or an empty username.
  • Invalid state: An order is shipped before payment is confirmed.
  • Programming mistakes: A list index is wrong, or a required object was not initialized.
  • Rule violations inside trusted code: A service method is called in the wrong sequence.
public void ship(Order order) {
    if (!order.isPaid()) {
        throw new IllegalStateException("order must be paid before shipping");
    }
}

Unchecked exceptions keep method signatures clean. They also make bugs visible. A hidden programming error should not be swallowed by a generic catch block. Honestly, it feels like wasted effort when code catches RuntimeException, logs “something failed,” and then lets the system continue in a corrupted state.

How to Decide Which One to Throw

A simple rule helps: if the caller can reasonably recover, use a checked exception. If the caller cannot recover and the code is wrong, use an unchecked exception.

Teams can ask these questions before creating an exception:

  1. Can the caller fix the problem right there? If yes, checked may fit.
  2. Is the failure caused by external systems? If yes, checked may help.
  3. Is the failure caused by a bad argument or broken state? If yes, unchecked is usually better.
  4. Will most callers just rethrow it? If yes, checked may add clutter.
  5. Is this part of a public API? If yes, the exception type deserves extra care.

Best Practices for Throwing Exceptions

Exception type matters, but message quality matters too. A vague exception can waste hours. The message should explain what failed and why. It should not expose secrets, tokens, or full customer data.

Better:

throw new IllegalArgumentException("customerId must not be blank");

Worse:

throw new RuntimeException("bad");

Developers should also avoid catching broad exceptions unless there is a clear reason. Catching Exception can hide serious defects. Catching RuntimeException can be even worse if the code then pretends everything is fine.

Custom exceptions can help when they add meaning. For example, PaymentDeclinedException is clearer than a generic Exception. But custom exceptions should not multiply without purpose. A project with 80 exception classes and no clear policy becomes painful fast.

Checked vs Unchecked in API Design

For public APIs, checked exceptions can document recoverable failure. A file parser might use InvalidFormatException so callers can report a useful error to the user. A payment SDK might use PaymentGatewayException for temporary provider issues.

For internal domain code, unchecked exceptions often fit better. If a service receives an invalid object from another trusted layer, that usually means a bug slipped through. The system should fail fast, log clearly, and let monitoring catch the issue.

Many modern Java frameworks lean toward unchecked exceptions. Spring, for example, wraps many data access failures in unchecked exceptions. This reduces boilerplate, though it also means teams need strong logging and clear error boundaries.

FAQ

What is the main difference between checked and unchecked exceptions?

A checked exception must be caught or declared with throws. An unchecked exception does not. Checked exceptions usually represent recoverable problems. Unchecked exceptions usually represent bugs, invalid input, or bad state.

Should all business errors be checked exceptions?

No. A recoverable business outcome may be checked, such as a declined payment that the caller can handle. A broken business invariant, such as shipping an unpaid order, is usually unchecked.

Is it bad to throw RuntimeException?

Not always. It is bad when used as a lazy default. Specific unchecked exceptions, such as IllegalArgumentException or IllegalStateException, are often better because they explain the real problem.

Should a method declare unchecked exceptions?

It is not required. Some teams still document them in Javadoc when they are part of the expected contract, especially for public APIs.

What is the safest rule of thumb?

If the caller can take a useful recovery action, use a checked exception. If the problem points to a coding mistake or invalid state, use an unchecked exception.