throw Keyword
The throw keyword lets you deliberately raise an exception at any point in your code. Instead of waiting for Java to detect a problem automatically, you take control and say: “Something went wrong here — stop normal execution and handle this.”
Why You Need throw
Java throws many exceptions automatically — a NullPointerException when you dereference null, an ArrayIndexOutOfBoundsException when you go past the end of an array. But what about your own business rules? If a method receives an age of -5, Java has no idea that’s invalid. You do. That’s where throw comes in.
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
this.age = age;
}
The moment Java reaches throw, it creates the exception object and immediately unwinds the call stack — no further statements in the method run.
Basic Syntax
throw new ExceptionType("message");
You always throw an instance of a Throwable subclass. The new keyword is almost always there because you’re creating a fresh exception object with a descriptive message.
public class ThrowBasicExample {
public static void checkScore(int score) {
if (score < 0 || score > 100) {
throw new IllegalArgumentException("Score must be 0–100, got: " + score);
}
System.out.println("Valid score: " + score);
}
public static void main(String[] args) {
checkScore(85); // fine
checkScore(150); // throws
}
}
Output:
Valid score: 85
Exception in thread "main" java.lang.IllegalArgumentException: Score must be 0–100, got: 150
at ThrowBasicExample.checkScore(ThrowBasicExample.java:4)
at ThrowBasicExample.main(ThrowBasicExample.java:10)
Note: Execution stops at the
throwline. The second call never gets past it unless the exception is caught somewhere up the call stack.
Checked vs Unchecked Exceptions
Before you throw, you need to know which kind of exception you’re dealing with.
| Category | Examples | Compile-time requirement |
|---|---|---|
| Unchecked (RuntimeException) | IllegalArgumentException, NullPointerException, ArithmeticException | None — you can throw freely |
| Checked (Exception, but not Runtime) | IOException, SQLException, ParseException | You must either catch it or declare it with throws |
Throwing an unchecked exception requires no special declaration:
throw new IllegalStateException("Cannot proceed in this state");
Throwing a checked exception requires the method signature to declare it with throws:
public void readFile(String path) throws IOException {
if (path == null) {
throw new IOException("File path must not be null");
}
// ...
}
Tip: Prefer unchecked exceptions for programming errors (bad arguments, invalid state) and checked exceptions for recoverable conditions that callers are expected to handle (file not found, network timeout).
Throwing Inside a try-catch Block
You can throw inside a catch block to re-throw the original exception or wrap it in a different one. Wrapping is called exception chaining and preserves the original cause for debugging.
public class ExceptionChaining {
public static int parseInt(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// wrap the low-level exception in a more meaningful one
throw new IllegalArgumentException("Invalid number: '" + value + "'", e);
}
}
public static void main(String[] args) {
System.out.println(parseInt("42"));
System.out.println(parseInt("abc")); // throws with cause
}
}
Output:
42
Exception in thread "main" java.lang.IllegalArgumentException: Invalid number: 'abc'
at ExceptionChaining.parseInt(ExceptionChaining.java:6)
...
Caused by: java.lang.NumberFormatException: For input string: "abc"
...
The second argument to the IllegalArgumentException constructor stores the original NumberFormatException as its cause. Tools like loggers and debuggers display both, giving you a complete picture.
Throwing Custom Exceptions
throw is most powerful when paired with custom exceptions that carry domain-specific meaning.
// Custom unchecked exception
public class InsufficientFundsException extends RuntimeException {
private final double amount;
public InsufficientFundsException(double amount) {
super("Insufficient funds. Tried to withdraw: " + amount);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientFundsException(amount);
}
balance -= amount;
System.out.println("Withdrew: " + amount + ", Remaining: " + balance);
}
public static void main(String[] args) {
BankAccount account = new BankAccount(100.0);
account.withdraw(60.0);
account.withdraw(80.0); // throws
}
}
Output:
Withdrew: 60.0, Remaining: 40.0
Exception in thread "main" InsufficientFundsException: Insufficient funds. Tried to withdraw: 80.0
Re-throwing the Same Exception
Sometimes you want to catch an exception, do something (like log it), and then let it propagate further. Just throw the caught reference again:
public void process(String data) {
try {
riskyOperation(data);
} catch (RuntimeException e) {
System.err.println("Logging error: " + e.getMessage());
throw e; // re-throw without wrapping
}
}
In Java 7+, re-throwing inside a catch (Exception e) block is smart enough to let the compiler track the actual exception types thrown inside the try, so you don’t have to broaden the throws declaration unnecessarily.
throw with finally
Be careful with throw inside finally blocks — it suppresses the original exception.
public static void riskyMethod() {
try {
throw new RuntimeException("Original");
} finally {
throw new RuntimeException("From finally"); // swallows "Original"!
}
}
Warning: Throwing from
finallydiscards the original exception entirely. The caller only sees the exception fromfinally. Avoid throwing fromfinallyunless you have a very specific reason.
Under the Hood
When you write throw new SomeException("msg"), the JVM performs these steps:
-
Object creation —
new SomeException("msg")allocates the exception object on the heap and invokes its constructor. The constructor chain ultimately callsThrowable(), which callsfillInStackTrace()— a native method that walks the current thread’s stack frames and stores them as an array inside the exception object. This is why constructing exceptions has a non-trivial cost. -
Stack unwinding — the JVM searches up the call stack for the nearest enclosing
catchblock whose declared type is compatible (viainstanceof). Each stack frame that has no matchingcatchis popped, and anyfinallyblocks on the way out are executed. -
Handler found — control jumps to the matching
catchblock. The exception object is bound to the catch parameter so your code can inspect it. -
No handler found — if unwinding reaches the bottom of the thread’s stack without finding a handler, the thread’s uncaught exception handler is invoked (by default it prints the stack trace and terminates the thread).
The bytecode instruction is simply athrow, a single opcode that takes the top-of-stack reference (the exception object) and triggers the unwinding process described above.
Tip: Because
fillInStackTrace()is expensive, some high-performance code pre-creates a singleton exception withnew MyException()and suppresses stack trace capture by overridingfillInStackTrace()to returnthis. Only do this for extremely hot paths where you’ve profiled a real bottleneck.
Common Pitfalls
- Swallowing with empty catch then re-throw: always include the cause when wrapping.
- Throwing
ExceptionorThrowabledirectly: too broad — callers can’t distinguish error types. Use specific exception classes. - Throwing from constructors without cleanup: if a constructor
throws, the object is never fully created, but resources partially acquired inside the constructor are not automatically released. Use try-with-resources or factory methods for resource-heavy constructors. - Using exceptions for flow control:
throwis for exceptional situations, not ordinary branching. Relying on exceptions for expected outcomes (e.g., usingNumberFormatExceptionto check if a string is numeric) is slow and hard to read.
Related Topics
- throws Keyword — declare the checked exceptions a method can propagate to its callers
- try-catch Block — the counterpart to
throw: catching and handling exceptions - finally Block — code that runs whether or not an exception was thrown
- Custom Exceptions — build domain-specific exception types to
throwwith meaning - throw vs throws — side-by-side comparison of two easily confused keywords
- Exception Propagation — understand how uncaught exceptions travel up the call stack