Skip to content
Java exceptions 7 min read

throws Keyword

The throws keyword appears in a method’s signature to announce that the method might raise one or more checked exceptions — and that the caller is responsible for dealing with them. Think of it as a method saying: “I might run into these problems; here’s your fair warning.”

Why throws Exists

Java splits exceptions into two categories: checked and unchecked. The compiler enforces handling for checked exceptions, and throws is how you participate in that contract.

Without throws, if you call a method that can raise a checked exception (like IOException) and you neither catch it nor declare it yourself, your code simply won’t compile. This is intentional — the compiler is making sure recoverable failures are never silently ignored.

import java.io.*;

public class WithoutThrows {
    // This does NOT compile — IOException is checked and not handled
    public static void readFile(String path) {
        FileReader fr = new FileReader(path); // compile error!
    }
}

Adding throws IOException to the signature tells the compiler: “I know this might throw — I’m deliberately passing the responsibility to my caller.”

import java.io.*;

public class WithThrows {
    public static void readFile(String path) throws IOException {
        FileReader fr = new FileReader(path); // compiles fine
        fr.close();
    }
}

Basic Syntax

accessModifier returnType methodName(parameters) throws ExceptionType1, ExceptionType2 {
    // method body
}

You list one or more exception types after the throws keyword, separated by commas. The exceptions you list must be subclasses of Throwable — and for this keyword to be required, they must be checked exceptions (subclasses of Exception but not RuntimeException).

import java.io.*;
import java.sql.*;

public class DataLoader {
    public void load(String filePath) throws IOException, SQLException {
        // may throw either exception; caller must handle both
    }
}

How Callers Must Respond

When you call a method that declares throws CheckedException, the compiler gives you exactly two choices:

Option 1 — Catch it:

import java.io.*;

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

    public static void readFile(String path) throws IOException {
        FileReader fr = new FileReader(path);
        fr.close();
    }
}

Option 2 — Propagate it (declare throws yourself):

import java.io.*;

public class PropagateExample {
    // main also declares throws — passes the buck to the JVM
    public static void main(String[] args) throws IOException {
        readFile("data.txt");
    }

    public static void readFile(String path) throws IOException {
        FileReader fr = new FileReader(path);
        fr.close();
    }
}

Note: Declaring throws in main means the JVM handles the exception if it occurs — it prints the stack trace and exits. This is convenient for quick programs but usually not right for production code.

A Complete Practical Example

Here is a self-contained example that shows throws working across multiple layers of a call stack:

import java.io.*;

public class FileProcessor {

    // Layer 3: reads a single line from a file
    public static String readFirstLine(String path) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(path));
        String line = reader.readLine();
        reader.close();
        return line;
    }

    // Layer 2: processes the content — propagates IOException
    public static void processFile(String path) throws IOException {
        String content = readFirstLine(path);
        System.out.println("First line: " + content);
    }

    // Layer 1: entry point — catches it here
    public static void main(String[] args) {
        try {
            processFile("notes.txt");
        } catch (IOException e) {
            System.out.println("Error processing file: " + e.getMessage());
        }
    }
}

Output (if the file exists):

First line: Hello, Java!

Output (if the file is missing):

Error processing file: notes.txt (No such file or directory)

The exception bubbles up through readFirstLineprocessFilemain, where it is finally caught. See Exception Propagation for a deeper look at how this unwinding works.

throws with Unchecked Exceptions

You can declare an unchecked exception (a RuntimeException subclass) in a throws clause, but the compiler never requires it. Doing so is purely informational — it can serve as documentation.

public int divide(int a, int b) throws ArithmeticException {
    return a / b; // throws ArithmeticException if b == 0
}

Tip: Documenting unchecked exceptions in throws is optional but can be a useful signal in public APIs. Most Java style guides recommend using Javadoc @throws tags for unchecked exceptions rather than the throws clause itself — this keeps the signature clean while still informing callers.

Declaring Supertype vs Subtype

You can declare a parent exception class in throws even if the method only actually throws a subclass. This is valid but can be overly broad.

import java.io.*;

public class SupertypeExample {
    // declares the supertype — works, but less precise
    public void open(String path) throws Exception {
        new FileReader(path);
    }
}

Prefer declaring the most specific type(s) so callers can write targeted catch blocks. Declaring just throws Exception forces callers to catch the very broad Exception, which obscures what can really go wrong.

throws in Interfaces and Inheritance

When you override a method from a superclass or interface, the rules around throws get stricter:

  • An overriding method can declare fewer or narrower checked exceptions than the parent.
  • An overriding method cannot add new or broader checked exceptions than the parent.
import java.io.*;

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

class Child extends Base {
    // OK: FileNotFoundException is a subclass of IOException (narrower)
    @Override
    public void connect() throws FileNotFoundException {}
}

class BadChild extends Base {
    // Compile error: SQLException is not a subclass of IOException
    // @Override
    // public void connect() throws SQLException {}
}

This rule upholds the Liskov Substitution Principle — code that holds a Base reference and catches IOException can still safely call connect() on any subclass.

Warning: If you forget this rule and try to add a new checked exception to an overriding method, the compiler will stop you with an error like: “connect() in BadChild cannot override connect() in Base; overridden method does not throw SQLException”.

Comparing throw and throws

These two keywords work as partners but do very different things:

Featurethrowthrows
PurposeActually raises an exceptionDeclares what a method might raise
PlacementInside the method bodyIn the method signature
Followed byAn exception instanceException class name(s)
Required for checked?Yes, to signal the errorYes, to compile if not caught
Can list multiple?No — throws one at a timeYes — comma-separated list
// throws in signature, throw in body — they go hand in hand
public void validateAge(int age) throws IllegalArgumentException {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
}

For a full side-by-side breakdown, see throw vs throws.

Under the Hood

throws is a purely compile-time construct. At the bytecode level, there is no instruction that corresponds to it — the JVM does not verify that a method only throws what its throws clause declares. You can confirm this with the javap tool: the exception table in a .class file records which exceptions each method handles (via catch blocks), not which it declares.

What throws actually does is inject metadata into the .class file’s Exceptions attribute. The Java compiler reads this attribute when it compiles calling code to enforce handling rules. Reflection can also read it at runtime via Method.getExceptionTypes().

import java.lang.reflect.*;

public class InspectThrows {
    public void riskyMethod() throws java.io.IOException, java.sql.SQLException {}

    public static void main(String[] args) throws Exception {
        Method m = InspectThrows.class.getMethod("riskyMethod");
        for (Class<?> ex : m.getExceptionTypes()) {
            System.out.println(ex.getName());
        }
    }
}

Output:

java.io.IOException
java.sql.SQLException

This is why some frameworks can inspect exception declarations at runtime to make routing or retry decisions — it is all stored in the class metadata.

Note: Because the JVM itself does not enforce throws, code compiled from other JVM languages (Kotlin, Scala, Groovy) can call Java methods that declare checked exceptions without handling them, since those languages treat all exceptions as unchecked.

Common Pitfalls

  • Declaring throws Exception — too broad. Callers are forced to catch or propagate Exception, which makes targeted error handling impossible. Be specific.
  • Confusing throws with throwthrows declares; throw fires. They must be consistent: if you throw new IOException() inside a method, you need throws IOException in the signature (or a surrounding catch).
  • Suppressing the clause to avoid caller burden — wrapping every checked exception in a RuntimeException just to skip throws is tempting but hides genuine failure modes from your API users.
  • Forgetting inheritance rules — when overriding, you cannot widen the checked exception set. Check the parent’s throws clause before writing your override.
  • throw Keyword — the counterpart that actually raises an exception inside a method body
  • throw vs throws — side-by-side comparison of two easily confused keywords
  • Exception Propagation — how declared exceptions travel up the call stack until caught
  • try-catch Block — how callers handle the exceptions you declare with throws
  • Custom Exceptions — define domain-specific checked or unchecked exceptions to declare and throw
  • Exception Handling — the full picture of Java’s exception handling mechanism
Last updated June 13, 2026
Was this helpful?