throw and throws in Java

throw raises an exception now; throws declares that a method may raise one and leaves the handling to the caller.

The difference in one table

throwthrows
PurposeRaises an exceptionDeclares what may be raised
WhereInside a method bodyIn the method signature
Followed byOne exception objectOne or more exception types
How manyOne at a timeAny number, comma separated
Ends executionYes, immediatelyNo, it is only a declaration

throw

public void setAge(int age) {
    if (age < 0 || age > 130) {
        throw new IllegalArgumentException("Age out of range: " + age);
    }
    this.age = age;
}

Execution stops at the throw. Anything after it in the same block is unreachable and will not compile.

throws

public String readConfig(Path path) throws IOException {
    return Files.readString(path);      // the checked exception travels outwards
}

throws is a declaration, not an action. It makes the possible failure part of the method contract, and the compiler then requires every caller to deal with it.

Handle or declare

// Option 1: handle it here
public String safeRead(Path path) {
    try {
        return Files.readString(path);
    } catch (IOException e) {
        return "";
    }
}

// Option 2: declare it and let the caller decide
public String read(Path path) throws IOException {
    return Files.readString(path);
}

For a checked exception those are the only two options. Unchecked exceptions need neither, although declaring one in throws is legal and occasionally used as documentation.

Fail fast argument checking

public Order(String reference, int quantity) {
    this.reference = Objects.requireNonNull(reference, "reference is required");
    if (quantity <= 0) {
        throw new IllegalArgumentException("Quantity must be positive, was " + quantity);
    }
    this.quantity = quantity;
}

Validate at the boundary and throw immediately. A failure at the point of the mistake is far cheaper to diagnose than a NullPointerException three layers away.

SituationThrow
An argument is invalidIllegalArgumentException
An argument is unexpectedly nullNullPointerException, via Objects.requireNonNull
The object is in the wrong state for this callIllegalStateException
An index is out of rangeIndexOutOfBoundsException
An operation is not supportedUnsupportedOperationException

Chaining the cause

try {
    return jdbc.query(sql);
} catch (SQLException e) {
    throw new DataAccessException("Query failed for note " + id, e);   // cause kept
}
DataAccessException: Query failed for note 42
    at com.example.notes.NoteRepository.find(NoteRepository.java:58)
Caused by: java.sql.SQLException: connection closed
    at ...

The Caused by chain is what makes wrapping useful. Without it you keep the abstraction and lose the diagnosis.

throws and overriding

class Loader {
    void load() throws IOException { }
}

class CachedLoader extends Loader {
    @Override void load() throws FileNotFoundException { }   // narrower, allowed
    // @Override void load() throws Exception { }            // broader, rejected
    @Override void load() { }                                // none, allowed
}

An override may declare the same checked exceptions, narrower ones, or none. It may never declare broader ones, because a caller holding the supertype has already written its handlers.

Rethrowing with precise types

public void run() throws IOException, SQLException {
    try {
        work();                       // declares both
    } catch (Exception e) {
        logger.warning("failed");
        throw e;                      // compiler knows only those two can occur
    }
}

Since Java 7 the compiler analyses what the try block can actually throw, so catching Exception and rethrowing does not force throws Exception on the signature.

Common mistakes

  • Writing throws where throw was meant, or the reverse.
  • Declaring throws Exception on everything, which tells callers nothing and forces them to catch too much.
  • Throwing a bare new Exception("...") instead of a specific type.
  • Losing the cause when wrapping.
  • Putting code after a throw in the same block.
  • Declaring checked exceptions that the method cannot actually throw.

Best practices

  • Throw the most specific standard exception that fits before inventing one.
  • Put the offending value in the message.
  • Declare only what a method can really throw, and keep the list short.
  • Always pass the cause when wrapping.
  • Validate arguments at the start of a method, not in the middle.

Practice

  1. Write a method that throws IllegalStateException when called before initialisation.
  2. Why can an overriding method not declare a broader checked exception?
  3. Convert a method that returns null on failure into one that throws, and say what improves.
  4. Wrap a low level exception in a domain one, keeping the cause, and read the resulting trace.
  5. Explain why throws Exception on a public API is poor design.

Conclusion

throw raises, throws declares. Fail fast with specific types, put useful detail in the message, keep the cause when wrapping, and declare only what a method genuinely produces.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.