Java throw and throws — Creating and Declaring Exceptions

4 minute read

throw and throws are two sides of Java’s exception mechanism: throw creates and throws an exception; throws declares that a method might throw one.


throw — Throwing an Exception

Use throw to explicitly raise an exception:

public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Age must be 0–150, got: " + age);
    }
    this.age = age;
}

Syntax

throw new ExceptionType("message");
  • throw takes an instance of Throwable (or its subclass).
  • Execution stops immediately and jumps to the nearest matching catch.
  • Code after throw in the same block is unreachable.

Common Exceptions to Throw

Exception When to Use
IllegalArgumentException Invalid method parameter
IllegalStateException Object in wrong state for the operation
NullPointerException Null where non-null is required
UnsupportedOperationException Method not implemented
IndexOutOfBoundsException Index outside valid range
public void process(String data) {
    if (data == null) {
        throw new NullPointerException("data must not be null");
    }
    // Or, Java 1.7+:
    Objects.requireNonNull(data, "data must not be null");
}

throws — Declaring Exceptions

Use throws in the method signature to declare that a method might throw a checked exception:

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

Why Is This Required?

Java’s compiler enforces the handle-or-declare rule for checked exceptions:

  1. Handle it with try-catch, OR
  2. Declare it with throws (pass the responsibility to the caller).
// Option 1: Handle it
public void loadConfig() {
    try {
        String content = readFile("config.yml");
    } catch (IOException e) {
        System.out.println("Config not found, using defaults");
    }
}

// Option 2: Declare it
public void loadConfig() throws IOException {
    String content = readFile("config.yml");
}

Multiple Exceptions

public void process() throws IOException, SQLException {
    // may throw either
}

throw vs throws — Summary

Feature throw throws
Location Inside method body In method signature
Purpose Throw an exception Declare possible exceptions
What follows An exception instance Exception class name(s)
Required for All exceptions (when you want to raise) Checked exceptions only
//                     throws (declaration)
//                        ↓
public void save() throws IOException {
    if (!isValid()) {
        throw new IOException("Invalid data");
        //  ↑
        // throw (creation)
    }
}

Rethrowing Exceptions

Catch an exception, do something, then rethrow:

public void process() throws IOException {
    try {
        readData();
    } catch (IOException e) {
        logger.error("Data read failed", e);
        throw e;  // rethrow the same exception
    }
}

Wrapping and Rethrowing

Convert a low-level exception to a higher-level one:

public User findUser(long id) throws ServiceException {
    try {
        return database.query("SELECT * FROM users WHERE id = ?", id);
    } catch (SQLException e) {
        throw new ServiceException("Failed to find user " + id, e);
        //                                              cause ──┘
    }
}

This preserves the cause chain — the original SQLException is accessible via getCause().


Chained Exceptions

Every exception can carry a cause — the underlying exception that triggered it:

try {
    connectToDatabase();
} catch (SQLException original) {
    RuntimeException wrapper = new RuntimeException("DB connection failed");
    wrapper.initCause(original);
    throw wrapper;
}

// Or, more commonly, use the constructor:
throw new RuntimeException("DB connection failed", original);

Accessing the Chain

catch (RuntimeException e) {
    System.out.println("Error: " + e.getMessage());
    System.out.println("Caused by: " + e.getCause().getMessage());
    e.printStackTrace();  // shows full chain
}

Stack trace output:

java.lang.RuntimeException: DB connection failed
    at App.main(App.java:10)
Caused by: java.sql.SQLException: Connection refused
    at Database.connect(Database.java:45)
    ... 2 more

When to throw vs When to Handle

Situation Action
Caller can recover throws — let them decide
No meaningful recovery throw unchecked exception
Library/API boundary throw domain-specific exception
Internal implementation detail Handle with try-catch
Precondition violated throw IllegalArgumentException
Programmer error (bug) throw unchecked (fail fast)

The “Throw Early, Catch Late” Principle

Throw exceptions as soon as a problem is detected:

public void transfer(Account from, Account to, double amount) {
    // Throw early — validate immediately
    if (from == null) throw new NullPointerException("from account");
    if (to == null) throw new NullPointerException("to account");
    if (amount <= 0) throw new IllegalArgumentException("amount must be positive");

    // ... proceed with transfer
}

Catch exceptions at the level where you can handle them meaningfully:

// Controller layer — catch and return error response
try {
    accountService.transfer(from, to, amount);
    return Response.ok("Transfer successful");
} catch (InsufficientFundsException e) {
    return Response.error(400, e.getMessage());
}

throws and Method Overriding

When overriding a method:

  • You can throw fewer checked exceptions.
  • You can throw narrower (subclass) checked exceptions.
  • You cannot throw new or broader checked exceptions.
class Parent {
    void read() throws IOException { }
}

class Child extends Parent {
    // ✅ Narrower exception
    void read() throws FileNotFoundException { }

    // ✅ No exception at all
    // void read() { }

    // ❌ Broader exception — compile error
    // void read() throws Exception { }
}

Practical Example — Input Validation

public class OrderService {

    public Order createOrder(String customerId, List<Item> items)
            throws InvalidOrderException {

        if (customerId == null || customerId.isBlank()) {
            throw new InvalidOrderException("Customer ID is required");
        }
        if (items == null || items.isEmpty()) {
            throw new InvalidOrderException("At least one item is required");
        }
        for (Item item : items) {
            if (item.getQuantity() <= 0) {
                throw new InvalidOrderException(
                    "Invalid quantity for item: " + item.getName());
            }
        }

        return new Order(customerId, items);
    }
}

Common Mistakes

Mistake Problem Fix
throw new Exception("...") Too generic Throw specific types
Losing the cause throw new X(msg) without cause Include original: new X(msg, e)
throws Exception on every method Defeats purpose of checked exceptions Declare specific types
Throwing in finally Masks original exception Avoid or use try-with-resources
Not documenting throws Callers don’t know what to expect Add @throws Javadoc

Key Takeaways

  1. throw creates and throws an exception instance.
  2. throws declares checked exceptions in the method signature.
  3. Always include a meaningful message and the cause when wrapping.
  4. Follow “throw early, catch late” — validate inputs immediately, handle at the right level.
  5. When overriding, you can only throw same or narrower checked exceptions.

What’s Next?

Next: Checked vs Unchecked Exceptions — the fundamental distinction, the debate, and when to use each.

Updated: