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
throwsinmainmeans 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 readFirstLine → processFile → main, 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
throwsis optional but can be a useful signal in public APIs. Most Java style guides recommend using Javadoc@throwstags for unchecked exceptions rather than thethrowsclause 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:
| Feature | throw | throws |
|---|---|---|
| Purpose | Actually raises an exception | Declares what a method might raise |
| Placement | Inside the method body | In the method signature |
| Followed by | An exception instance | Exception class name(s) |
| Required for checked? | Yes, to signal the error | Yes, to compile if not caught |
| Can list multiple? | No — throws one at a time | Yes — 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 propagateException, which makes targeted error handling impossible. Be specific. - Confusing
throwswiththrow—throwsdeclares;throwfires. They must be consistent: if youthrow new IOException()inside a method, you needthrows IOExceptionin the signature (or a surroundingcatch). - Suppressing the clause to avoid caller burden — wrapping every checked exception in a
RuntimeExceptionjust to skipthrowsis 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
throwsclause before writing your override.
Related Topics
- 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