Command Palette

Search for a command to run...

PHASE 7Intermediate ~32 min· topic 4 of 8

Topic 7.4

throw and throws

In one line

throw is a statement that throws one exception object right now. throws is part of a method signature that warns callers which checked exceptions the method may pass up to them.

Think of it like this

A referee and a match rulebook. When a player commits a foul, the referee blows the whistle: play stops immediately, right there. That's throw. The rulebook printed before the match says "this game may be stopped for fouls, rain or injury" so everyone can prepare. That's throws: a warning written in advance, not an action.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

`throw`
A statement that throws one exception object immediately: throw new IllegalStateException("closed");.
`throws`
A clause in a method signature listing checked exceptions the method may pass to its caller.
Guard clause
An if at the start of a method that rejects bad input or bad state by throwing, before any real work happens.
Fail fast
Detect a problem as early as possible and stop with a clear error, instead of continuing and failing confusingly later.
Precondition
Something that must be true before a method runs, such as 'amount is positive'. Guard clauses check preconditions.
Static type
The type the compiler sees for a variable or expression, from its declaration. The object's real class may be more specific.
Unreachable statement
Code the compiler can prove will never run, like a line right after throw or return. Java treats it as an error.
`@throws`
A Javadoc tag that documents which exceptions a method throws and when.

Step by step

01throw: stop right here

Write throw followed by an exception object. Execution leaves the current block at once, and the search for a catch begins (Topic 7.1).

Put the useful facts in the message: what was wrong and what the bad value was. "amount must be > 0, was -5" saves the reader a debugging session; "invalid" doesn't.

Main.javawhole filejava
static void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("age must be 0..150, was " + age);
    }
    System.out.println("age set to " + age);   // only reached for valid ages
}

02throws: warn the caller

A method that can let a checked exception escape must say so. The throws clause is part of the method's contract, just like its parameters and return type.

A method can list several types, separated by commas: throws IOException, InterruptedException. Listing a superclass (throws Exception) covers all its subclasses but tells the caller much less, so prefer the specific types.

Main.javawhole filejava
/**
 * Saves the text to disk.
 * @throws IOException if the disk is full or the file can't be written
 * @throws IllegalArgumentException if text is empty (unchecked, documented only)
 */
static void save(String text) throws IOException {
    if (text.isEmpty()) throw new IllegalArgumentException("text is empty");
    // ... write the file, which may throw IOException
}

03Pick the right exception type

Use the standard types; every Java developer already knows what they mean. Inventing InvalidAmountException for a simple bad argument adds a class without adding information (Topic 7.5 covers when a custom type is worth it).

Pick the right exception typediagram
Rendering diagram…

04Nothing runs after throw

javac's reachability analysis (JLS 14.22) knows throw can't complete normally, so a statement after it in the same block is an error, not a warning.

terminal
$ javac Main.java
── expected output ──
Main.java:4: error: unreachable statement
return 1;
^
1 error

05Helpers that always throw: return the exception

The compiler analyses one method at a time. It doesn't know that fail(...) always throws, so it thinks the end of the method can be reached without a return value.

Make the helper build and return the exception, and throw it at the call site: throw fail("not a digit: " + c);. Now the compiler sees a throw and is satisfied, and the stack trace still points at the helper's line where the object was created.

Main.javawhole filejava
static int parseDigit(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    fail("not a digit: " + c);           // compiler: missing return statement
}

static int parseDigitFixed(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    throw invalid("not a digit: " + c);  // fine: the method clearly ends here
}

static IllegalArgumentException invalid(String msg) {
    return new IllegalArgumentException(msg);
}
terminal
$ javac Main.java
── expected output ──
Main.java:5: error: missing return statement
}
^
1 error

06Throwing from a constructor

If a constructor throws, new never returns a reference. The memory was allocated, but nothing points to it, so the garbage collector reclaims it. Callers can never see a half-built object.

That's why validating in constructors (and in records' compact constructors, Topic 4.9) makes invalid objects impossible. One caveat for later: if the constructor leaked this somewhere before throwing (registered itself in a static list), that partial object stays reachable. Never do that.

Throwing from a constructordiagram
Rendering diagram…

07throw in a switch expression

In a switch expression every arm must produce a value or throw. default -> throw new IllegalArgumentException(...) is the standard way to reject values you don't handle, and it keeps the switch exhaustive (Java 14).

Main.javawhole filejava
static int priority(String level) {
    return switch (level) {
        case "low" -> 1;
        case "normal" -> 2;
        case "high" -> 3;
        default -> throw new IllegalArgumentException("unknown level: " + level);
    };
}

Try it yourself

  1. 1

    Add a rule

    In the guard-clause example, add a rule that one withdrawal can't exceed 1000 and test withdraw 1500 (unfreeze the account first). Which exception type fits best: argument or state? Predict the line, then run.

  2. 2

    Swap the guard order

    Move the frozen check above the amount <= 0 check. Then call withdraw(-5) on a frozen account. Predict which message you get. Why does the order of guard clauses matter for the error a user sees?

  3. 3

    Break the helper idiom

    In the second example, change throw badInput(String.valueOf(c)); to just badInput(String.valueOf(c));. Predict the compiler error, compile, then restore it.

Code & diagrams

Guard clauses that fail fast New tab

Each failed call leaves the balance untouched, because every check runs before the change.

Sign in to run this example in your browser.

Expected output

withdraw 200: ok
withdraw -5: IllegalArgumentException - amount must be > 0, was -5
withdraw 900: IllegalStateException - insufficient funds: balance 300, asked 900
withdraw 10: IllegalStateException - account of Asha is frozen
new Account(null): NullPointerException - owner must not be null
new Account(-1): IllegalArgumentException - opening balance < 0: -1
balance left: 300
throws clauses, throw in a switch, and the helper idiom Java 14+ New tab
Sign in to run this example in your browser.

Expected output

priority(high) = 3
unknown input: urgent
parseDigit('7') = 7
unknown input: x
saved 'a'
saved 'b'
saveAll stopped: nothing to save
throw null, and the static type of a throw New tab

The compiler made main handle Exception, but at run time the more specific catch matched the real object.

Sign in to run this example in your browser.

Expected output

throw null gives: NullPointerException
message: Cannot throw exception because "Main.missing" is null
caught as IllegalStateException: really a runtime exception
Standard precondition helpers (fragment) Java 9+java
import java.util.Objects;

static String initials(String first, String last) {
    Objects.requireNonNull(first, "first");          // NPE with message "first"
    Objects.requireNonNull(last, "last");
    return "" + first.charAt(0) + last.charAt(0);
}

static int pick(int[] values, int i) {
    Objects.checkIndex(i, values.length);            // Java 9: IndexOutOfBoundsException
    return values[i];                                // "Index 5 out of bounds for length 3"
}

static String nameOrDefault(String name) {
    return Objects.requireNonNullElse(name, "guest"); // Java 9: no throw, a default
}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Code after throw

Write throw new IllegalStateException("x"); followed by return 1; in the same block.

terminal
$ javac Main.java
── what you'll see ──
Main.java:4: error: unreachable statement
return 1;
^
1 error

Break #2

Throw a checked exception without declaring it

In main, write throw new Exception("boom"); without a throws clause.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: unreported exception Exception; must be caught or declared to be thrown
throw new Exception("boom");
^
1 error

Break #3

A helper that always throws, used as the last statement

End an int method with a call to static void fail(String msg) { throw new IllegalArgumentException(msg); }.

terminal
$ javac Main.java
── what you'll see ──
Main.java:5: error: missing return statement
}
^
1 error

Myth vs fact

Myth

throw and throws do the same thing.

Fact

throw is an action that happens now, with one object. throws is a declaration in a signature that warns callers. A method can declare throws and never actually throw, and can throw unchecked exceptions it never declares.

Myth

You must list every exception in throws.

Fact

Only checked exceptions are required. Unchecked ones can be listed for documentation but the compiler ignores them; describing them with Javadoc @throws is the convention.

Myth

throws Exception everywhere is a safe default.

Fact

It forces every caller to catch or declare the very broad Exception, hides which failures are really possible, and makes callers catch runtime bugs together with real I/O errors. Declare the specific types.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    In bytecode, throw is the single instruction athrow, which pops a reference and throws it; if the reference is null, athrow throws NullPointerException instead. The throws clause doesn't generate any instructions at all: it's only the Exceptions attribute on the method.

  • ▸

    Guard clauses are a form of design by contract. Objects.requireNonNull is a tiny method the JIT inlines, so the check costs about a compare-and-branch; validating at public API boundaries and trusting internal callers is the usual balance. assert statements (disabled unless the JVM runs with -ea) suit internal invariants, never argument checks on public methods.

  • ▸

    Since the JVM doesn't check throws, exceptions can be thrown that a method never declared (Topic 7.3's sneaky throw). Proxies created by java.lang.reflect.Proxy defend against this by wrapping an undeclared checked exception from the handler in UndeclaredThrowableException.

  • ▸

    Constructing the exception captures the stack trace in the constructor, so throw fail(...) reports the helper's frame on top. Libraries like Guava's Preconditions and the JDK's Objects.checkIndex accept this. Readers quickly learn to skip that one extra frame, and the convenience is worth it.

Remember this

  1. 1

    throw expression; throws the exception object the expression evaluates to. The expression must have a type that is Throwable or a subclass. Most of the time you create the object on the spot: throw new IllegalArgumentException("age must be >= 0, was " + age);. The statement never completes normally, so the compiler treats any statement right after it in the same block as unreachable and rejects it.

  2. 2

    throws appears after a method's or constructor's parameter list: void save(String s) throws IOException. It lists the checked exceptions that can escape the method, so the compiler can apply catch-or-declare at every call site (Topic 7.3). You may also list unchecked exceptions there for documentation, but the compiler ignores them. Javadoc's @throws tag is where you describe both kinds and when they happen.

  3. 3

    The most common use of throw is a guard clause: check arguments and state at the top of a method and throw before doing any damage. Use IllegalArgumentException for a bad argument, IllegalStateException when the object is in the wrong state for this call, NullPointerException (via Objects.requireNonNull(x, "x")) for a forbidden null, IndexOutOfBoundsException (via Objects.checkIndex, Java 9) for a bad index, and UnsupportedOperationException for an operation this object doesn't support. This is called failing fast.

  4. 4

    Whether a throw needs a throws clause depends on the static type of the expression, not the object's real class at run time. Exception e = new IllegalStateException(); throw e; must be caught or declared as Exception, because the compiler only knows the variable's type. (Topic 7.7 shows a special exception to this for rethrowing a catch parameter.)

  5. 5

    throw is a statement, but since Java 14 it can also be a switch expression arm: default -> throw new IllegalArgumentException(...). The compiler understands that a method ending in throw doesn't need a return. It does not understand that a helper method always throws, so fail("..."); at the end of a method still gives missing return statement. The idiom is to have the helper return the exception and write throw fail("...");.

  6. 6

    Throwing null (throw null; or a variable that is null) doesn't throw nothing: the JVM throws a NullPointerException instead. Throwing from a constructor means the object is never handed to anyone: new doesn't return, and the half-built object becomes garbage. That's what makes constructor validation safe.

Explain it without notes

01

What is the difference between throw and throws?

02

What is a guard clause, and which standard exceptions would you use in one?

03

Does throwing new IllegalStateException() through a variable of type Exception need a throws clause? Why?

04

Why does a method that ends with a call to an always-throwing helper fail to compile, and how do you fix it?

Practice

01

Write static double sqrtChecked(double x) that throws IllegalArgumentException with the value in the message for negative input. Call it with 9 and -4, catching and printing the message.

02

Write a class Door with open(), close() and lock(), where open() on a locked door and lock() on an open door throw IllegalStateException. Run a sequence that ends in an error and print each step.

03

Write static char grade(int marks) using a switch expression over marks / 10 that returns 'A' for 9 and 10, 'B' for 8, 'C' for 6 and 7, 'F' for 0..5, and throws for anything else. Test with 95, 72, 40 and 130.

Trade-offs

  • ↔

    Validating every argument in every method makes failures precise but adds noise and a little cost; validating at public boundaries and trusting private helpers is the common compromise.

  • ↔

    Standard exception types are instantly understood but carry little structure; a custom type (Topic 7.5) can carry fields like an error code, at the cost of another class to maintain.

  • ↔

    Declaring specific checked exceptions documents exactly what can fail but couples callers to implementation details (switching from files to a database changes IOException to SQLException); wrapping in a domain exception hides that, at the cost of an extra layer.

Done when you can

  • Done when you can explain throw vs throws in one sentence each.

  • Done when you write guard clauses with the right standard exception and an informative message.

  • Done when you know that the static type of a throw expression decides whether it must be declared.

  • Done when you can use throw in a switch expression and the throw helper(...) idiom.

  • Done when you can explain why throwing from a constructor never exposes a half-built object.