Skip to content
Java exceptions 6 min read

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 throw line. 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.

CategoryExamplesCompile-time requirement
Unchecked (RuntimeException)IllegalArgumentException, NullPointerException, ArithmeticExceptionNone — you can throw freely
Checked (Exception, but not Runtime)IOException, SQLException, ParseExceptionYou 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 finally discards the original exception entirely. The caller only sees the exception from finally. Avoid throwing from finally unless you have a very specific reason.

Under the Hood

When you write throw new SomeException("msg"), the JVM performs these steps:

  1. Object creationnew SomeException("msg") allocates the exception object on the heap and invokes its constructor. The constructor chain ultimately calls Throwable(), which calls fillInStackTrace() — 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.

  2. Stack unwinding — the JVM searches up the call stack for the nearest enclosing catch block whose declared type is compatible (via instanceof). Each stack frame that has no matching catch is popped, and any finally blocks on the way out are executed.

  3. Handler found — control jumps to the matching catch block. The exception object is bound to the catch parameter so your code can inspect it.

  4. 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 with new MyException() and suppresses stack trace capture by overriding fillInStackTrace() to return this. 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 Exception or Throwable directly: 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: throw is for exceptional situations, not ordinary branching. Relying on exceptions for expected outcomes (e.g., using NumberFormatException to check if a string is numeric) is slow and hard to read.
  • 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 throw with meaning
  • throw vs throws — side-by-side comparison of two easily confused keywords
  • Exception Propagation — understand how uncaught exceptions travel up the call stack
Last updated June 13, 2026
Was this helpful?