Skip to content
Java exceptions 7 min read

throw vs throws

throw and throws look almost identical, but they serve completely different purposes. throw is an action — you use it to actually raise an exception. throws is a declaration — you use it in a method signature to warn callers that the method might raise certain exceptions. Mixing them up is one of the most common beginner stumbles in Java.

Quick Comparison

Featurethrowthrows
What it isA statementA keyword in a method signature
Where it appearsInside a method bodyAfter the parameter list, before {
What it doesRaises an exception right nowDeclares what exceptions might propagate
How many exceptionsExactly one at a timeCan list multiple, comma-separated
Works withAny Throwable instanceClass names only (no new, no instance)
Required for unchecked?N/A (you just throw it)No — optional for unchecked exceptions
Required for checked?Use it inside the methodYes — compiler enforces this

throw — Raising an Exception

You use throw when something has gone wrong and you want execution to stop and unwind the call stack. You always throw an instance, created with new.

public class AgeValidator {
    public static void validateAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        System.out.println("Age accepted: " + age);
    }

    public static void main(String[] args) {
        validateAge(25);   // prints normally
        validateAge(-3);   // throws immediately
    }
}

Output:

Age accepted: 25
Exception in thread "main" java.lang.IllegalArgumentException: Invalid age: -3
	at AgeValidator.validateAge(AgeValidator.java:4)
	at AgeValidator.main(AgeValidator.java:10)

Notice that throw takes a fully constructed object (new IllegalArgumentException(...)), not just a class name. Execution halts at the throw line — any code below it in the method is unreachable.

Note: throw can appear anywhere inside a method body, inside a constructor, or even inside a static initializer block. There is no restriction on where it lives, as long as it appears in executable code.

throws — Declaring Propagated Exceptions

throws belongs in the method signature. It tells the compiler — and your fellow developers — “this method may not handle certain checked exceptions itself; the caller must deal with them.”

import java.io.FileReader;
import java.io.IOException;

public class FileProcessor {
    // declares that it might throw IOException
    public static void openFile(String path) throws IOException {
        FileReader reader = new FileReader(path);
        System.out.println("File opened: " + path);
        reader.close();
    }

    public static void main(String[] args) {
        try {
            openFile("data.txt");
        } catch (IOException e) {
            System.out.println("Could not open file: " + e.getMessage());
        }
    }
}

throws IOException on openFile tells the compiler two things:

  1. openFile may propagate an IOException to its caller.
  2. Callers must either catch it or declare their own throws IOException.

Without that throws declaration, the compiler refuses to compile — FileReader constructor throws a checked IOException and Java insists you handle it somewhere.

Tip: throws can list multiple exception types separated by commas: throws IOException, SQLException, ParseException. List only exceptions a caller genuinely needs to know about — don’t dump every possible exception into the signature.

Side-by-Side Example

This single class shows both keywords working together. The inner method uses throw to raise the exception; the outer method uses throws to pass responsibility to its caller.

import java.io.IOException;

public class ThrowVsThrowsDemo {

    // throws declares the checked exception — the caller must handle it
    public static void readConfig(String filename) throws IOException {
        if (filename == null || filename.isBlank()) {
            // throw actually raises the exception with a message
            throw new IOException("Filename must not be blank");
        }
        System.out.println("Reading config: " + filename);
    }

    public static void main(String[] args) {
        // caller is forced to handle IOException because of 'throws'
        try {
            readConfig("app.properties"); // fine
            readConfig("");               // throws IOException
        } catch (IOException e) {
            System.out.println("Config error: " + e.getMessage());
        }
    }
}

Output:

Reading config: app.properties
Config error: Filename must not be blank

The flow is: throws in the signature → compiler enforces handling → throw inside the body → JVM unwinds the stack at runtime.

Checked vs Unchecked: When throws Is Mandatory

This is where the rules differ most sharply.

Checked exceptions — throws is required

If you use throw with a checked exception (any Exception subclass that is not a RuntimeException), the compiler demands a matching throws declaration unless the exception is caught in the same method.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateParser {
    // ParseException is checked — throws is mandatory here
    public static Date parse(String dateStr) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.parse(dateStr); // internally throws ParseException
    }
}

Unchecked exceptions — throws is optional

You can freely throw unchecked exceptions (RuntimeException and its subclasses) without any throws declaration. Adding throws for unchecked exceptions is allowed but uncommon — it’s mainly used as self-documenting API design.

public class Calculator {
    // throws ArithmeticException is optional here — just for documentation
    public static int divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("Cannot divide by zero");
        }
        return a / b;
    }
}

Warning: Never declare throws Error or throws Throwable in a real API. Callers cannot meaningfully respond to Error subclasses (like OutOfMemoryError), and declaring Throwable erases all useful type information for callers.

throws Does NOT Mean the Exception Is Always Thrown

A common misconception: a method with throws SomeException in its signature doesn’t have to throw that exception. It simply reserves the right to do so. The exception may or may not occur at runtime.

public static void maybeThrow(boolean fail) throws IOException {
    if (fail) {
        throw new IOException("Deliberate failure");
    }
    System.out.println("No exception this time.");
}

Both calls below are valid:

maybeThrow(false); // runs fine — no exception thrown
maybeThrow(true);  // throws IOException

The caller must still be prepared to handle IOException even though it only fires on fail == true.

Inheriting and Overriding: throws Narrows, Never Widens

When you override a method, the overriding method’s throws clause can only be the same or narrower than the parent’s. It can never introduce new or broader checked exceptions.

import java.io.FileNotFoundException;
import java.io.IOException;

class Base {
    public void read() throws IOException { }
}

class Child extends Base {
    // OK — FileNotFoundException is a subtype of IOException (narrower)
    @Override
    public void read() throws FileNotFoundException { }
}

class Bad extends Base {
    // COMPILE ERROR — Exception is broader than IOException
    // @Override
    // public void read() throws Exception { }
}

This rule ensures that code written against Base still compiles when it is given a Child at runtime — the Liskov Substitution Principle applied to exceptions.

Note: The narrowing rule applies only to checked exceptions. You can add, remove, or change unchecked exceptions freely in an override because the compiler doesn’t enforce them.

Under the Hood

How throw works in bytecode

The Java compiler translates throw someException; into a single bytecode instruction: athrow. This opcode pops the top reference from the operand stack (the exception object), validates that it’s a non-null Throwable, and triggers the JVM’s exception dispatch mechanism — searching the current method’s exception table for a matching handler, then unwinding frames until one is found (executing finally blocks along the way).

The most expensive part of throwing is not athrow itself but the constructor call that precedes it. Throwable’s constructor calls fillInStackTrace(), a native method that snapshots the entire call stack into the exception object. This is why exceptions should not be used for ordinary flow control — constructing them is orders of magnitude slower than a simple if-branch.

How throws works at compile time

throws has zero runtime cost. It is purely a compile-time signal stored in the method’s descriptor inside the .class file (in the Exceptions attribute of the method_info structure). The JVM itself does not enforce throws at runtime — that enforcement is entirely the Java compiler’s job. This is why you can call Java methods from other JVM languages (like Kotlin or Scala) without worrying about checked exceptions; those languages simply ignore the Exceptions attribute.

Common Pitfalls

  • Using throw without throws for a checked exception — the compiler will stop you, but beginners sometimes try it.
  • Writing throws new IOException()throws takes a class name, not an instance. Writing new after throws is a syntax error.
  • Declaring throws but never throwing — valid but misleading. It forces every caller to add try-catch for no reason. Revisit whether the declaration is still needed.
  • Catching an exception in the same method that declares throws — you can catch it locally AND still have throws in the signature (for other code paths), but if every code path catches it, the throws declaration is dead weight.

For a deeper look at each keyword on its own, see the throw keyword and throws keyword pages.

Last updated June 13, 2026
Was this helpful?