Q9 of 40 · Core Java
What is the difference between throw and throws in Java?
Short answer
Short answer: `throw` is a statement that actually raises an exception at runtime. `throws` is a declaration in a method signature that warns callers this method may propagate a checked exception. `throw` is action; `throws` is a contract.
Detail
throw and throws look almost identical but serve completely different purposes.
throw is an executable statement. When the JVM reaches a throw statement, it immediately stops normal execution, creates (or re-throws) an exception object, and begins unwinding the call stack looking for a matching catch block. You use it to signal that something has gone wrong: throw new IllegalArgumentException("id must be positive").
throws appears in a method signature and is a compile-time contract. It tells the compiler — and callers — that this method may propagate a checked exception that the caller must handle or declare in its own throws clause. It does not cause the exception to be thrown; it just documents the possibility. Unchecked exceptions (RuntimeException and its subclasses) can be thrown without a throws declaration — the compiler doesn't force you to declare them.
Why this matters in test code: test methods in JUnit 5 can declare throws Exception in their signature. This lets you call checked-exception-throwing code directly without try-catch boilerplate — JUnit catches and reports the exception as a test failure. Without the throws declaration, you'd need a try-catch in every test, cluttering the assertion logic.
The distinction also matters when reading stack traces: throw is the line that caused the exception; throws is just the declaration that propagated it.
// EXAMPLE
ThrowVsThrows.java
import java.io.IOException;
// throws — contract: caller must handle or re-declare IOException
public String readConfig(String path) throws IOException {
return Files.readString(Path.of(path)); // may throw IOException
}
// throw — action: raise exception right now
public int parsePort(String value) {
int port = Integer.parseInt(value);
if (port < 1 || port > 65535) {
throw new IllegalArgumentException(
"Port must be 1-65535, got: " + port);
}
return port;
}
// JUnit 5 test: declare throws Exception to avoid try-catch boilerplate
@Test
void configFileIsReadable() throws IOException {
String content = readConfig("src/test/resources/config.json");
assertThat(content).contains("baseUrl");
// IOException propagates to JUnit if thrown — reported as test failure
}