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
ifat 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
throworreturn. 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.
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.
/**
* 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).
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.
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.
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);
}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.
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).
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
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
Swap the guard order
Move the
frozencheck above theamount <= 0check. Then callwithdraw(-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
Break the helper idiom
In the second example, change
throw badInput(String.valueOf(c));to justbadInput(String.valueOf(c));. Predict the compiler error, compile, then restore it.
Code & diagrams
Each failed call leaves the balance untouched, because every check runs before the change.
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: 300Expected output
priority(high) = 3
unknown input: urgent
parseDigit('7') = 7
unknown input: x
saved 'a'
saved 'b'
saveAll stopped: nothing to saveThe compiler made main handle Exception, but at run time the more specific catch matched the real object.
Expected output
throw null gives: NullPointerException
message: Cannot throw exception because "Main.missing" is null
caught as IllegalStateException: really a runtime exceptionimport 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.
Break #2
Throw a checked exception without declaring it
In main, write throw new Exception("boom"); without a throws clause.
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); }.
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,
throwis the single instructionathrow, which pops a reference and throws it; if the reference isnull,athrowthrowsNullPointerExceptioninstead. Thethrowsclause doesn't generate any instructions at all: it's only theExceptionsattribute on the method. - ▸
Guard clauses are a form of design by contract.
Objects.requireNonNullis 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.assertstatements (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 byjava.lang.reflect.Proxydefend against this by wrapping an undeclared checked exception from the handler inUndeclaredThrowableException. - ▸
Constructing the exception captures the stack trace in the constructor, so
throw fail(...)reports the helper's frame on top. Libraries like Guava'sPreconditionsand the JDK'sObjects.checkIndexaccept this. Readers quickly learn to skip that one extra frame, and the convenience is worth it.
Remember this
- 1
throw expression;throws the exception object the expression evaluates to. The expression must have a type that isThrowableor 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
throwsappears 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@throwstag is where you describe both kinds and when they happen. - 3
The most common use of
throwis a guard clause: check arguments and state at the top of a method and throw before doing any damage. UseIllegalArgumentExceptionfor a bad argument,IllegalStateExceptionwhen the object is in the wrong state for this call,NullPointerException(viaObjects.requireNonNull(x, "x")) for a forbiddennull,IndexOutOfBoundsException(viaObjects.checkIndex, Java 9) for a bad index, andUnsupportedOperationExceptionfor an operation this object doesn't support. This is called failing fast. - 4
Whether a
throwneeds athrowsclause 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 asException, because the compiler only knows the variable's type. (Topic 7.7 shows a special exception to this for rethrowing a catch parameter.) - 5
throwis 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 inthrowdoesn't need areturn. It does not understand that a helper method always throws, sofail("...");at the end of a method still givesmissing return statement. The idiom is to have the helper return the exception and writethrow fail("...");. - 6
Throwing
null(throw null;or a variable that isnull) doesn't throw nothing: the JVM throws aNullPointerExceptioninstead. Throwing from a constructor means the object is never handed to anyone:newdoesn't return, and the half-built object becomes garbage. That's what makes constructor validation safe.
Explain it without notes
What is the difference between throw and throws?
What is a guard clause, and which standard exceptions would you use in one?
Does throwing new IllegalStateException() through a variable of type Exception need a throws clause? Why?
Why does a method that ends with a call to an always-throwing helper fail to compile, and how do you fix it?
Practice
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.
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.
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
IOExceptiontoSQLException); wrapping in a domain exception hides that, at the cost of an extra layer.
Done when you can
Done when you can explain
throwvsthrowsin 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
throwin a switch expression and thethrow helper(...)idiom.Done when you can explain why throwing from a constructor never exposes a half-built object.