The throw keyword is how your own code signals that something has gone wrong. The previous lessons in this section focused on handling exceptions raised by library code. This lesson covers the other direction: deciding that the current method has hit an impossible situation and refusing to continue. We'll look at the syntax, when to raise an exception yourself, which standard exception types fit which situations, and the rules the compiler enforces around throw.
throw vs throws: One Letter, Two Different ThingsTwo keywords look almost identical and do completely different jobs.
| Keyword | Where it appears | What it does |
|---|---|---|
throw | Inside a method body, as a statement | Raises (transfers control to) an exception at the point it appears |
throws | After a method's parameter list, in the signature | Declares that the method can propagate certain checked exceptions to its caller |
A throw statement is an action: it happens at runtime, the moment that line is executed. A throws clause is a declaration: it tells the compiler what checked exceptions a method is allowed to let escape.
A throw statement has one shape:
The expression must produce a Throwable, which in practice means an instance of Exception, RuntimeException, Error, or one of their subclasses. The most common case is a fresh new SomeException(...) created right there in the statement.
A guard inside a setter on a product:
Two important things happen here. The first call, setQuantity(10), takes the else branch (technically the implicit one, since we don't have an explicit else) and assigns the field. The second call, setQuantity(-2), hits the if, executes the throw, and stops the method right there. Control jumps out of setQuantity and back into main. Since main doesn't catch the exception, the JVM prints the stack trace and exits.
The print statement after the second setQuantity call never runs. Once throw fires, execution doesn't return to the place the throw appeared.
The point of throw is that the current method has noticed something it can't or shouldn't handle. The three common reasons:
1.5 makes no sense. A quantity of -3 makes no sense. The method is being asked to do something the rest of the program shouldn't expect it to handle silently.In each case, the alternative to throwing is worse. Returning a sentinel value like -1 or null pushes the problem onto every caller and is easily ignored. Logging a warning and continuing produces wrong data that may go unnoticed for weeks. A throw makes the failure loud and immediate, at the line where the problem was first detectable.
A discount setter that rejects out-of-range percentages:
The first call applies a 20% discount and gets a sensible final price. The second call asks for 150%, which would make getFinalPrice return a negative number. The setter refuses, the JVM prints the stack trace, and the program exits before the broken state can spread.
A new exception class is rarely needed. The standard library provides a handful of well-known runtime exception types, and each fits a particular kind of problem. The table below lists the five most common.
| Exception | When to throw it |
|---|---|
IllegalArgumentException | An argument's value isn't legal for this method (out of range, wrong format, empty when non-empty is required). |
IllegalStateException | The object is in a state where the operation isn't allowed (cancelling a delivered order, popping an empty cart). |
NullPointerException | A required reference argument is null and the method can't proceed without it. |
UnsupportedOperationException | The operation is part of the type's contract but this implementation doesn't support it. |
ArithmeticException | A numeric operation failed for a reason your code detected (divide by zero, overflow that matters). |
Picking the right one matters because the exception class itself is documentation. A caller who sees IllegalStateException knows the input was fine but the object wasn't ready. A caller who sees IllegalArgumentException knows the call itself was wrong. Lumping everything under RuntimeException loses that information.
Each of these is unchecked (a subclass of RuntimeException), which means the compiler doesn't require declaration in a throws clause or wrapping calls in try/catch. That fits the use case: these exceptions signal programming errors the caller is expected to prevent, not handle.
throw Stops the Current MethodA throw statement is an unconditional transfer of control. It's in the same family as return, break, and continue: once the JVM hits it, execution doesn't fall through to the next statement.
The compiler knows this. Any statement that the compiler can prove will run only after a throw is dead code, and the compiler will reject it.
What's wrong with this code?
The compiler refuses to build this file:
The throw always fires before the println could possibly run, so the println is unreachable. The fix is to remove the dead line, or to put it inside an if branch that doesn't always execute:
Now the println is reachable, because the throw only runs when the guard matches. The compiler accepts this version.
Once throw fires, the JVM walks back up the call stack looking for a catch block that handles the exception's type. The first match wins. If nothing in the stack catches it, the JVM prints the stack trace and ends the thread.
The diagram below traces a throw from a deep method call up through three levels of handlers.
Reading top to bottom: main calls placeOrder, which calls validate, which calls setQuantity(-2). The setter throws. The JVM checks setQuantity for a matching catch (none), then walks up to validate (no catch there either), then to placeOrder, where a matching catch block exists. Control jumps into that catch. Frames between the throw site and the handler are unwound: their local variables are gone, their execution is abandoned.
Here's that flow as actual code:
validate throws. placeOrder's try block was active, so the matching catch runs and prints the friendly message. Control then returns to main normally, and main continues past placeOrder to print "After placeOrder". The exception never reaches main's frame, because placeOrder consumed it.
IllegalStateException is the right pick when the arguments are fine but the object isn't ready to do what's being asked. Two classic examples from the e-commerce world: cancelling an order that's already delivered, and removing an item from a cart that's empty.
The order moves through PLACED -> SHIPPED -> DELIVERED. Calling cancel on a delivered order is a real bug in the calling code, not a recoverable condition. Throwing IllegalStateException makes that bug visible at the exact line where it was triggered, with a message that names the order and the current state. The caller has every piece of information needed to fix the problem.
The empty-cart version:
The first remove succeeds because there's one item. The second fails because the cart is empty. The IllegalStateException message describes the problem in domain terms ("an empty cart"), which is easier to act on than a generic IndexOutOfBoundsException from the underlying list.
throw Must Be a ThrowableThe compiler enforces that whatever is thrown is an instance of Throwable or one of its subclasses (Exception, RuntimeException, Error, and so on). A plain String, an int, or any non-exception object can't be thrown.
This fails to compile:
The error is:
The fix is to wrap the message in an exception:
Now the expression after throw is an IllegalArgumentException, which is a RuntimeException, which is an Exception, which is a Throwable. The compiler is satisfied.
null: Compiles, CrashesThere's one case where the compiler lets throw past the type check but the JVM still has to do something at runtime: throwing a null reference of an exception type.
The variable error is declared as IllegalArgumentException, which the compiler accepts as a legal target for throw. At runtime, the value is null. The JVM has nothing to actually throw, so it throws a NullPointerException in place of the missing exception.
The lesson here's simple: don't throw null. Always pass a real exception instance to throw, usually a freshly constructed one. When an exception variable might be null, fix the source so that the variable is always set, or guard the throw site so it only fires when there's something real to throw. throw null; literally typed into the source is a particularly nasty bug because reading the line says nothing about what will actually happen.
catchSometimes a method catches an exception, does some partial work (like logging, releasing a resource, or adding context), and then wants to let the exception keep propagating. The pattern uses throw on the caught reference:
validateCart throws. checkout catches the exception, runs its partial-handling code (printing the diagnostic line), and then re-raises the same exception with throw e;. The exception continues unwinding up to main, which doesn't catch it, so the JVM prints the trace.
The stack trace still points at validateCart line 16, the original throw site. Re-throwing the caught instance preserves the original trace. That preservation is usually the right behavior, because the original line is where the trouble actually started, and rewriting it with throw new IllegalStateException(...) would hide that information.
This pattern (catch, do something, re-throw) is sometimes called the final-catch-rethrow pattern. Common reasons to use it:
try block was responsible for.For releasing resources, the try-with-resources construct is usually a cleaner option than catch-cleanup-rethrow. The catch-cleanup-rethrow pattern fits cases where cleanup isn't quite the right framing and the exception should keep propagating.
catchA different but related pattern is catching one exception type and throwing a different one in its place. This usually happens when a low-level method throws something specific (a parsing failure, an I/O error) and the caller needs a higher-level exception that fits the domain.
Integer.parseInt("twelve") throws NumberFormatException. The catch block decides that callers of parseQuantity shouldn't have to know about NumberFormatException, since they're working with quantities rather than raw strings. So it throws an IllegalArgumentException with a clearer message and a domain-appropriate type.
The example above does lose information. The new exception doesn't reference the original NumberFormatException, so the stack trace shows only the rethrow site, not the original parse failure. A standard technique called exception chaining fixes this by attaching the original exception as a "cause" to the new one. The takeaway is that throw inside a catch can construct a new exception of any type, not just re-throw the caught one.
throw With Earlier ConstructsA throw works fine inside a try block. The surrounding try-catch then has the option to handle that exception locally or to let it propagate. Putting it together with finally:
The order of operations is worth tracing carefully. process prints its Starting line, then enters the inner try. The guard fails, so the throw runs. Control leaves the try block, but before the exception propagates out of the method, the finally runs and prints Releasing resources for process. The exception continues up into main, where the surrounding catch block handles it and prints the friendly message.
The finally ran even though the try block ended abruptly via throw.
A common temptation when writing a setter or service method is to "tolerate" bad input. Maybe the method clamps a negative quantity to zero without warning. Maybe it accepts an invalid discount percentage and treats it as 0.5 instead. Maybe it accepts a null product name and substitutes the string "unknown". All of these compile. All of them ship bugs to production.
The problem isn't the substitution itself. The problem is that the substitution hides a real defect in the calling code. Some caller passed -2 for a quantity, which means there's a bug somewhere up the call chain that produced a negative number. If the setter accepts and clamps it, that bug doesn't surface until much later, often in a totally unrelated part of the system (a report shows the wrong total, a customer's invoice has the wrong amount). By the time someone notices, the original cause is impossible to find.
A throw at the first detection point fixes this. The exception's stack trace points at the misbehaving caller. The error message describes exactly what went wrong. The defect can be reproduced, located, and fixed in minutes instead of days. The cost is that some code that "would have worked" now throws an exception, but in practice that code was already wrong; it wasn't being honest about it.
The shorthand for this is fail fast: detect problems at the earliest possible point and stop, rather than letting them slip through and corrupt later state. The throw keyword is the main tool the language provides for this.
A short summary of the rules covered in this lesson, in one place:
| Rule | Detail |
|---|---|
| Syntax | throw <expression>; where <expression> evaluates to a Throwable. |
| Control flow | Unconditional transfer. Statements after throw in the same block are unreachable and rejected by the compiler. |
| Null throws | throw null; compiles but raises a NullPointerException at runtime. Don't write it. |
| Re-throw | throw e; inside a catch propagates the caught exception with its original stack trace intact. |
| Replace | throw new SomeOther(...); inside a catch raises a different exception. The original trace is lost unless chained. |
| Standard types | Prefer IllegalArgumentException, IllegalStateException, NullPointerException, UnsupportedOperationException, or ArithmeticException when one fits. |
| Pairing | throw is the statement that raises an exception. throws is the clause that declares which checked exceptions a method can propagate. |
9 quizzes