HomeBlogHow to Throw an Exception in Java: throw vs throws and Other...

How to Throw an Exception in Java: throw vs throws and Other Exception-Handling Patterns

Author

Date

Category

Use throw inside a method to actually raise an exception, and use throws in a method declaration to tell callers that an exception may be passed back to them.

TLDR: throw is an action; throws is a declaration. For example, throw new IllegalArgumentException("Invalid age") stops the current flow, while public void readFile() throws IOException tells the caller to handle or re-declare the risk. In a payment service handling 100,000 requests per day, even a 0.5% validation failure rate means 500 exceptions or error responses must be handled cleanly. Use exceptions for real failure cases, not for normal branching.

What throw Does in Java

The throw keyword is used when your code has detected a problem and wants to create or pass on an exception. It must be followed by an object that is an instance of Throwable, usually an Exception or RuntimeException.

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    this.age = age;
}

This code fails fast. That is good. Bad input should not quietly move through the system and cause stranger errors later.

The basic pattern is simple:

  • Create an exception object.
  • Add a clear message.
  • Use throw to stop the current path.
  • Let a caller handle it, or let the program fail with a stack trace.

It drives me crazy when code throws new RuntimeException("error") with no context. That saves five seconds while writing the code and can cost an hour during production debugging.

text java exception flow stack trace error handling

What throws Does in Java

The throws keyword appears in a method signature. It does not throw anything by itself. It tells the compiler and other developers that the method may pass an exception to its caller.

public String readConfig(String path) throws IOException {
    return Files.readString(Path.of(path));
}

Here, IOException is a checked exception. The caller must either catch it or declare it again with throws.

try {
    String config = readConfig("app.properties");
} catch (IOException e) {
    logger.error("Could not read config file", e);
}

This is the main difference:

  • throw happens inside a method body.
  • throws is written in the method declaration.
  • throw raises one specific exception object.
  • throws may list one or more exception types.

Checked vs Unchecked Exceptions

Java separates exceptions into checked and unchecked types. This is where many developers lose time, mostly because the compiler is strict in some places and silent in others.

Checked exceptions must be handled or declared. Examples include IOException, SQLException, and ParseException. They usually represent recoverable problems outside the program’s direct control, such as missing files or database failures.

Unchecked exceptions extend RuntimeException. Examples include IllegalArgumentException, NullPointerException, and IllegalStateException. They usually mean the program has a bug, invalid state, or bad input.

public void transfer(BigDecimal amount) {
    if (amount == null) {
        throw new IllegalArgumentException("Amount is required");
    }
    if (amount.signum() <= 0) {
        throw new IllegalArgumentException("Amount must be positive");
    }
}

You do not need to declare IllegalArgumentException with throws. You can, but it often adds noise without helping the caller.

When to Use throw

Use throw when continuing would be unsafe, misleading, or wrong. Common cases include invalid arguments, impossible states, failed security checks, unrecoverable external failures, and missing required data.

Good uses of throw include:

  • Rejecting a negative quantity in an order.
  • Stopping a login attempt with a missing username.
  • Failing when a required configuration value is absent.
  • Wrapping a low-level exception with business context.
try {
    paymentGateway.charge(card, amount);
} catch (GatewayTimeoutException e) {
    throw new PaymentFailedException("Payment gateway timed out", e);
}

This wrapping pattern keeps the original cause while adding meaning. The caller does not need to know every detail of the gateway library. It only needs to know the payment failed.

monitor showing java programming hiring developers code review technical interview 1

When to Use throws

Use throws when a method cannot sensibly handle a checked exception at its own level. File access is a classic example. A low-level utility that reads bytes should not decide whether the application should retry, show a message, or stop startup.

public byte[] loadCertificate(Path path) throws IOException {
    return Files.readAllBytes(path);
}

The service or application layer can then make the decision.

try {
    byte[] cert = loadCertificate(certPath);
} catch (IOException e) {
    throw new IllegalStateException("Application cannot start without certificate", e);
}

This is a clean split. The utility reports the technical failure. The higher layer decides its business impact.

Common Exception-Handling Patterns

1. Fail fast on invalid input. Check method arguments early. Throw IllegalArgumentException or NullPointerException with a precise message.

public User findUser(String id) {
    if (id == null || id.isBlank()) {
        throw new IllegalArgumentException("User id is required");
    }
    return repository.findById(id);
}

2. Catch only what you can handle. Do not catch Exception just to print it and continue. That can hide data loss, failed writes, and security issues.

3. Preserve the cause. When you wrap an exception, pass the original exception as the cause.

throw new ReportGenerationException("Could not create monthly report", e);

4. Use custom exceptions carefully. A custom exception is helpful when it represents a real domain failure, such as InsufficientFundsException. It is less useful when it simply renames RuntimeException.

5. Log once, at the right layer. Logging the same exception in four places creates noisy alerts and bloated logs. In a system processing 2 million events per hour, repeated stack traces can add gigabytes of useless log data each day.

Bad Patterns to Avoid

Do not swallow exceptions.

try {
    sendEmail();
} catch (Exception e) {
    // ignored
}

This code lies. The email failed, but the system acts as if nothing happened. Expect to waste time on fake success reports if this style spreads.

Do not use exceptions for ordinary decisions. If a map may not contain a key, check it. Do not force a NullPointerException and catch it as flow control.

Do not expose sensitive data in messages. Exception text can reach logs, dashboards, support tickets, or API responses. Avoid passwords, tokens, full card numbers, and private user data.

black flat screen computer monitor secure coding java logs sensitive data

Practical Rules for Production Code

  • Use throw to raise a specific failure now.
  • Use throws to declare checked failures a caller must handle.
  • Use unchecked exceptions for programming errors and invalid arguments.
  • Use checked exceptions when recovery is realistic and expected.
  • Write messages that explain what failed and which safe identifier was involved.
  • Keep stack traces by preserving the original cause.

A serious Java codebase treats exceptions as part of its contract. They are not decoration. They shape how services fail, recover, alert, and protect data. Use throw with intent, use throws with restraint, and make every exception message useful to the person who gets paged at 2:14 a.m.

Recent posts