What does the throws keyword do
The throws keyword is declared in a method or constructor signature to specify which checked exceptions the method may potentially propagate to its callers.
🟢 Junior Level
Short Interview Answer (30 seconds)
The throws keyword is declared in a method or constructor signature to specify which checked exceptions the method may potentially propagate to its callers.
It forms an explicit component of the method’s public API contract. The presence of throws enforces the compiler’s “Catch or Specify” requirement: any calling code must either:
- Handle the exception locally using a
try-catchblock. - Re-declare the exception (or its supertype) in its own method header via
throws.
Key Distinctions: throw vs. throws
| Feature | throw |
throws |
|---|---|---|
| Location | Inside method bodies or code blocks | In method or constructor signature headers |
| Role | Imperative Action: instantiates and triggers exception dispatch | Declarative Contract: advertises potential escaping exception types |
| Syntax | throw new IllegalArgumentException("Invalid"); |
public void process() throws IOException; |
| Operand | A single instantiated object (Throwable instance) |
A comma-delimited list of exception class types |
Canonical Java 21 Example
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class ConfigurationService {
// Method declares that it may fail with a checked IOException
public List<String> readLines(String filePath) throws IOException {
return Files.readAllLines(Path.of(filePath));
}
// Approach 1: Caller handles the exception locally
public void loadLocal(String path) {
try {
List<String> lines = readLines(path);
System.out.println("Loaded lines: " + lines.size());
} catch (IOException e) {
System.err.println("Failed to read configuration: " + e.getMessage());
}
}
// Approach 2: Caller delegates responsibility further up the call stack
public void loadAndPropagate(String path) throws IOException {
List<String> lines = readLines(path);
// Process configuration lines...
}
}
🟡 Middle Level
Method Overriding & The Liskov Substitution Principle (LSP)
When a subclass overrides a superclass method or implements an interface method, polymorphism in Java strictly restricts the throws clause in compliance with LSP:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ METHOD OVERRIDING THROWS COMPATIBILITY │
├────────────────────────────────────────────────────────────────┬───────────────────────┤
│ Overriding Method Signature │ Compiler Verdict │
├────────────────────────────────────────────────────────────────┼───────────────────────┤
│ Same checked exceptions as parent │ ✅ Allowed │
├────────────────────────────────────────────────────────────────┼───────────────────────┤
│ Narrower / Subclass checked exceptions (Covariant Exceptions) │ ✅ Allowed │
├────────────────────────────────────────────────────────────────┼───────────────────────┤
│ Omits checked exceptions entirely │ ✅ Allowed │
├────────────────────────────────────────────────────────────────┼───────────────────────┤
│ Any unchecked exceptions (RuntimeException / Error) │ ✅ Allowed │
├────────────────────────────────────────────────────────────────┼───────────────────────┤
│ New checked exceptions not declared in parent │ ❌ COMPILATION ERROR │
├────────────────────────────────────────────────────────────────┼───────────────────────┤
│ Broader checked supertype (e.g., Exception instead of IOEx) │ ❌ COMPILATION ERROR │
└────────────────────────────────────────────────────────────────┴───────────────────────┘
public interface DataRepository {
void persist() throws IOException;
}
public class FileRepository implements DataRepository {
// 1. Covariant narrowing: FileNotFoundException is a subclass of IOException
@Override
public void persist() throws java.io.FileNotFoundException { }
}
public class MemoryRepository implements DataRepository {
// 2. Omitting: Implementation is guaranteed never to fail with checked errors
@Override
public void persist() { }
}
public class DatabaseRepository implements DataRepository {
// 3. ❌ ILLEGAL: Adding an unrelated checked exception breaks polymorphism!
// Compilation Error: overridden method does not throw java.sql.SQLException
// @Override
// public void persist() throws SQLException { }
}
Architectural Rationale: A caller holding a reference to DataRepository expects only IOException. If DatabaseRepository were allowed to throw SQLException, the caller’s catch (IOException e) block would miss it, causing an unhandled runtime failure and breaking type safety.
Unchecked Exceptions in throws Clause
Java syntax permits declaring unchecked exceptions (RuntimeException and Error) in the throws clause:
// Technically legal syntax, but considered an architectural code smell:
public void calculateRate(int rate) throws IllegalArgumentException, NullPointerException {
// ...
}
Engineering Rule: Avoid declaring unchecked exceptions in throws. The compiler does not verify them, they add visual clutter, and they mislead developers into believing handling is mandatory. Instead, document unchecked exceptions via Javadoc tags:
/**
* @throws IllegalArgumentException if rate is negative
*/
public void calculateRate(int rate) { ... }
JVM Bytecode Internals: The Exceptions Attribute
The JVM runtime does not enforce throws contracts during execution.
When javac compiles a method declaring throws, it records the exception class symbolic references in a metadata attribute table named Exceptions:
// Disassembled via javap -v ConfigurationService.class:
public java.util.List<java.lang.String> readLines(java.lang.String) throws java.io.IOException;
descriptor: (Ljava/lang/String;)Ljava/util/List;
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
...
Exceptions:
throws java.io.IOException
This attribute exists solely for static analysis by the javac compiler. The bytecode instruction athrow operates identically whether an exception was declared in Exceptions or not. Reflection, bytecode instrumentation, or generic type erasure (Sneaky Throws) can emit checked exceptions from methods that declare no throws clause without JVM runtime errors.
🔴 Senior Level
Constructor Inheritance Trap: Superclass Exceptions
Constructors follow opposite rules from methods regarding throws:
public class BaseResource {
public BaseResource() throws IOException {
// Allocates OS file descriptor
}
}
public class SecureResource extends BaseResource {
// Subclass constructor CANNOT remove or narrow the parent exception!
// It MUST declare IOException (or a wider superclass like Exception)!
public SecureResource() throws IOException, GeneralSecurityException {
super(); // Mandatory first statement; cannot be enclosed in try-catch!
}
}
Unlike regular methods, a subclass constructor cannot eliminate checked exceptions declared by its parent constructor. Because Java requires super(...) to be the absolute first statement of a constructor body, wrapping super() in a try-catch block is syntactically prohibited.
Generic Exception Signatures (<E extends Throwable>)
Java Generics enable dynamic, precise exception transparency in functional interfaces and utility decorators:
@FunctionalInterface
public interface ThrowingSupplier<T, E extends Throwable> {
T get() throws E;
}
public final class RetryExecutor {
// E is dynamically inferred based on the lambda's exact thrown checked exception
public static <T, E extends Throwable> T executeWithRetry(ThrowingSupplier<T, E> action) throws E {
try {
return action.get();
} catch (Throwable t) {
// Under generic type erasure, javac allows propagating E without re-wrapping
@SuppressWarnings("unchecked")
E typedException = (E) t;
throw typedException;
}
}
public static void main(String[] args) {
try {
// Compiler precisely infers E as java.sql.SQLException
String data = executeWithRetry(() -> queryDatabase());
} catch (java.sql.SQLException e) {
System.err.println("Database query failed: " + e.getErrorCode());
}
}
private static String queryDatabase() throws java.sql.SQLException { return "ok"; }
}
Binary Compatibility vs. Source Compatibility in Library Evolution
Altering a public method’s throws clause has severe backward-compatibility implications:
- Adding a Checked Exception: Breaks Source Compatibility. Downstream consumer applications will immediately fail compilation upon upgrading the library version.
- Removing a Checked Exception: Maintains Source and Binary Compatibility. Compiled client
.classfiles continue linking without errors at runtime. - The “Throws Umbrella” Anti-Pattern: Declaring
throws Exceptionorthrows Throwableon public interfaces destroys domain intent, forces callers into writing genericcatch (Exception e)handlers, and masks critical runtime bugs.
4 Tricky Questions
1. What happens if a class implements two interfaces that declare the same method, but with different throws clauses: InterfaceA { void run() throws IOException; } and InterfaceB { void run() throws SQLException; }?
Answer: The implementing class must satisfy the intersection of both contracts. Because a client may invoke the method via either InterfaceA or InterfaceB, the implementation cannot throw IOException (which breaks InterfaceB) nor SQLException (which breaks InterfaceA).
Therefore, the implementing method cannot declare any checked exceptions (or can only declare checked exceptions that are common subclasses of both IOException and SQLException, which in this case do not exist). The implementing class must declare public void run() { ... } with no throws clause.
2. Can you declare throws on a static or private method? Does polymorphism apply to them?
Answer: Yes, throws can be declared on static and private methods to enforce the “Catch or Specify” rule on their callers.
However, polymorphism and LSP rules do not apply to them:
privatemethods are not inherited or overridden; a child class method with the same name is an unrelated, independent method and may declare completely different exceptions.staticmethods are hidden (shadowed), not overridden, so theirthrowsclauses are resolved statically based on the compile-time variable reference.
3. Why can an overriding method eliminate throws IOException, but a subclass constructor cannot eliminate throws IOException from its superclass constructor?
Answer: An overriding method can eliminate the exception because a caller expecting an IOException is completely safe if no exception is ever thrown (LSP compliance).
A subclass constructor, however, must execute super(...) to initialize the superclass state before its own constructor body executes. Since Java language rules forbid wrapping super() in a try-catch block, any exception thrown by super() will inevitably propagate out of the subclass constructor. Therefore, the subclass constructor must declare that exception in its own throws clause.
4. What happens at the JVM bytecode level if a method throws a checked exception that was NOT listed in its throws attribute?
Answer: The JVM executes normally! At the bytecode level, the JVM executes the athrow instruction, which pops an object reference from the operand stack and initiates exception handling by traversing the method’s Exception_table and caller stack frames.
The JVM runtime never checks whether the exception class matches the Exceptions attribute in the classfile. The checked exception requirement is strictly a static compile-time constraint enforced by javac.
🎯 Interview Cheat Sheet
Core Concepts Matrix
┌───────────────────────────┬──────────────────────────────────────────┬──────────────────────────────────────────┐
│ Principle │ Valid / Recommended │ Invalid / Code Smell │
├───────────────────────────┼──────────────────────────────────────────┼──────────────────────────────────────────┤
│ Method Overriding │ Narrowing (FileNotFoundException) │ Broadening (throws Exception) │
│ Overriding Removal │ Completely removing throws │ Adding new checked exception (SQLEx) │
│ Subclass Constructor │ Broadening or declaring parent exception │ Attempting to suppress parent throws │
│ Unchecked Exceptions │ Document via @throws Javadoc tag │ Declaring throws RuntimeException │
│ Generalization │ Specific domain exceptions │ "Throws Umbrella" (throws Exception) │
│ Generic Exceptions │ <E extends Throwable> throws E │ Raw untyped generic throws │
└───────────────────────────┴──────────────────────────────────────────┴──────────────────────────────────────────┘
Key Takeaways
throwsadvertises potential checked exceptions in a method or constructor header, enforcing compile-time handling.- In overridden methods, the
throwsclause can narrow (covariant) or completely eliminate checked exceptions, but can never widen or add new checked exceptions. - Subclass constructors cannot remove checked exceptions declared by their superclass constructor because
super()cannot be wrapped in atry-catch. - Declaring unchecked exceptions (
RuntimeException) inthrowsis technically permitted but discouraged. - The
throwsclause is stored in the.classmetadata as theExceptionsattribute and is enforced purely byjavac, not the JVM runtime.
Red Flags to Avoid
- ❌ Confusing
throw(imperative statement) withthrows(declarative method header clause). - ❌ Claiming that subclasses can declare new checked exceptions when overriding interface or parent methods.
- ❌ Believing that the JVM runtime verifies
throwsdeclarations when executing theathrowinstruction. - ❌ Putting
throws Exceptionon public service interfaces as a shortcut to avoid writing specific exception classes.
Related Topics
- Can you throw a checked exception without declaring throws — Sneaky Throws mechanics
- What is the difference between checked and unchecked exceptions — Compiler vs runtime enforcement
- What is a checked exception and when to use it — Architectural application of checked exceptions
- Can you rethrow an exception — Rethrowing and precise rethrow in Java 7+
- What is exception wrapping — Translation of exceptions across layers