The handle-or-declare rule
Student can state the handle-or-declare rule for checked exceptions, identify whether a given exception is checked or unchecked, and propagate a checked exception up the call stack by declaring throws on each enclosing method.
Java's compiler enforces a simple rule for checked exceptions (JLS §11.2): every method that calls something which can throw a checked exception must either handle it (try/catch) or declare it (throws on the method header). Doing neither is a compile error.
The rule applies transitively up the call stack until either:
1. A method catches the exception (the chain ends there), or 2. The exception reaches main and is declared on main's throws clause (the JVM prints a stack trace and exits), or 3. The exception is RuntimeException or its subclasses (unchecked: no declaration required).
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class Demo {
public static void main(final String[] args) throws FileNotFoundException {
readAndPrint("data.txt"); // could throw: declared on main
}
public static void readAndPrint(final String filename) throws FileNotFoundException {
final Scanner sc = new Scanner(new File(filename)); // could throw: declared here
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
sc.close();
}
}
If readAndPrint removed its throws clause, the call to new Scanner(new File(...)) would be a compile error. If main removed its throws clause, the call to readAndPrint would be a compile error. The rule is mechanical: each method's header must acknowledge every checked exception its body can let escape.
Why this is called the "course route" for CS1 labs
CS1 file-reading programs are usually one-shot: read a file, print results, exit. There is no meaningful recovery from FileNotFoundException: if the file's missing, the program should fail loudly. The simplest implementation is to declare throws FileNotFoundException on every method that opens a file, including main. The JVM's default uncaught-exception handler prints a stack trace and exits with a non-zero status, which is exactly the right behavior.
The alternative (wrapping every file open in try/catch(FileNotFoundException e) { System.out.println(e); return; }) adds noise without adding value. Lab 11 (and most CSCD 210 file labs) ban try/catch precisely to keep the "course route" visible.
When try/catch is the right choice
When there is a recovery: the user mistyped a filename and you want to re-prompt; one file out of several failed and you want to keep processing the others; the program is a long-running service that should not exit on one bad input. In those cases the catch block contains the recovery code.
Unchecked exceptions are different
IllegalArgumentException, NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException: all RuntimeException subclasses. The compiler does not require declarations for these. You can throws them if you want to document them, but it is optional. Methods that throw IllegalArgumentException typically do not put it on the header; the Javadoc lists it instead.
In other languages
- Python: no checked/unchecked distinction; all exceptions are unchecked. Documentation is the only signal.
- C++: had checked exceptions briefly (
throw(...)specifications), deprecated in C++11, removed in C++17. - C#: no checked exceptions. Documentation only.
Java is the major exception (so to speak): its checked-exception design is influential, controversial, and not copied by newer languages.
throws (declaration) vs throw (action)
Student can distinguish the keywords throws (on a method header, declares; takes type names) from throw (in a method body, fires; takes an exception instance), recognize the obscure compile errors that result from confusing them, and identify which of the two is being used in a given snippet.
Java has two near-identical keywords that mean different things. They differ by one character and appear in different positions; mixing them up is one of the most common compile errors in week 7.
| Keyword | Where it appears | What it does | |---------|------------------|--------------| | throws (with s) | on a method header, after the parameter list | declares that the method may throw the listed checked exceptions | | throw (no s) | inside a method body, as a statement | throws the exception object on the right of the keyword |
// `throws` (with s): on the header: declares the contract
public static int countLines(final String filename) throws FileNotFoundException {
if (filename == null || filename.isEmpty()) {
// `throw` (no s): in the body: actually fires an exception
throw new IllegalArgumentException("filename must not be null or empty: " + filename);
}
final Scanner sc = new Scanner(new File(filename)); // this constructor can throw FNFE
int count = 0;
while (sc.hasNextLine()) { sc.nextLine(); count++; }
sc.close();
return count;
}
Three properties of the pair:
1. throws lists types; throw produces an instance. throws FileNotFoundException, IOException declares two types the method may throw. throw new IllegalArgumentException("...") produces one specific exception instance and starts propagation. 2. throws is only for checked exceptions in practice. It is legal to declare throws RuntimeException on a header, but the convention is to document unchecked exceptions in the Javadoc only (@throws tag). The header's throws is reserved for checked exceptions. 3. throw works for any exception type. Whether checked or unchecked, throw new SomeException(...) fires it. The compiler then checks: if the type is checked, was it declared on the enclosing method's throws clause, or caught by a try/catch? Otherwise compile error.
Compile errors students see
The two most common mistakes:
// Wrong: missed the `s`
public static void foo() throw FileNotFoundException { }
// error: not a statement
// (the parser sees `throw` as a body keyword, not a header keyword)
// Wrong: added an `s`
public static void foo() throws FileNotFoundException {
throws new FileNotFoundException("..."); // inside the body
}
// error: ';' expected
// (the parser saw `throws` as a method-modifier and expected the rest of a declaration)
Both errors are obscure because the parser misinterprets the keyword's role. The fix is always to check which position the keyword is in:
- On the method header, between
)and{:throwswiths. - Inside the method body, before an exception expression:
throwwithouts.
The same exception class works with both
throw new FileNotFoundException("...") (action) and throws FileNotFoundException (declaration) refer to the same class. The grammar uses different keywords because the operations are different (the type system vs the runtime). A method that throws FNFE will have both:
public static int readData() throws FileNotFoundException { // declaration
if (someBadCondition) {
throw new FileNotFoundException("expected data file missing"); // action
}
// ...
}
In other languages
- Python:
raise SomeException(...)is the action; no separate declaration keyword (Python has no checked exceptions). - C++:
throw e;is the action; older C++ hadvoid f() throw(SomeException)declarations but this is removed in modern C++. - C#:
throw new SomeException(...)is the action; no declaration keyword. - JavaScript:
throw e;(oftenthrow new Error(...)) is the action; no declaration.
Java is the rare language with two keywords for the two concerns. The asymmetry comes from the checked-exception design.
When throws on main is the right CS1 answer
Student can decide between throws FileNotFoundException on main and a try/catch block based on whether the program has a meaningful recovery path, predict the JVM's default behavior when main propagates an uncaught exception, and identify the two scenarios where try/catch is the right answer in CSCD 210.
The CSCD 210 convention for checked exceptions in introductory file labs is declare, do not catch: every method that opens a file declares throws FileNotFoundException on its header, all the way up to and including main. This is the "course route." It is the default answer until a lab explicitly asks for try/catch.
public class WordCount {
public static void main(final String[] args) throws FileNotFoundException {
final Scanner sc = new Scanner(new File("data.txt"));
// ... read and process ...
sc.close();
}
}
The reasoning, in three steps:
1. The program is one-shot. A CS1 file-reading program runs to completion or exits. There is no interactive recovery, no retry loop, no fallback path. The user runs the program with a filename; the program either succeeds or fails. 2. There is no meaningful catch body. What would the catch do? Print "file not found" and exit. The JVM's default uncaught-exception handler already prints a clean message and exits with non-zero status, and includes the file path in its diagnostic. Writing a catch that does the same thing is duplication. 3. The throws clause is honest documentation. public static void main(...) throws FileNotFoundException says, by its type signature, "this program can fail to find its file." That signal is true and useful; hiding it inside a swallowing catch makes the failure mode less visible.
What the JVM does when main throws
The JVM has a default uncaught-exception handler. When main (or any thread's top-level method) throws an exception that no catch matched, the handler:
1. Prints the exception's class and message to System.err. 2. Prints the stack trace below the message. 3. Exits the JVM with status code 1 (non-zero, conventionally "failure").
The result for throws FileNotFoundException looks like this:
Exception in thread "main" java.io.FileNotFoundException: data.txt (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
at java.base/java.util.Scanner.<init>(Scanner.java:639)
at WordCount.main(WordCount.java:8)
The first line is exactly what the catch body would have printed; the rest is the stack trace, which is useful for debugging. The exit code 1 lets shell scripts detect the failure (./wordcount data.txt && echo OK || echo FAIL).
When throws on main is NOT the right answer
The two CSCD 210 scenarios where try/catch replaces throws:
1. The program prompts the user for the filename and should re-prompt on failure. Interactive recovery is meaningful; the catch body lives in a loop that re-reads the filename. 2. The program processes multiple files and one bad file should not stop the others. The catch body skips this file and continues to the next.
Both scenarios are absent in Labs 8, 9, 10b, and 11: the canonical CS1 file labs. All four use throws FileNotFoundException on main.
CSCD 211 will revisit this
In CSCD 211 the file-handling programs become long-running services (database connections, network sockets, web servlets). The throws route is rarely correct there because exiting the JVM on the first I/O failure is unacceptable. CSCD 211 students learn to wrap the I/O in try/catch and propagate via Optional, Result-style return types, or logged-then-rethrown exceptions. CSCD 210 keeps the simpler model.
In other languages
- Python: Python has no checked-exception concept; every method implicitly "throws" everything. The
if __name__ == "__main__"block does not need any declaration. Uncaught exceptions inmainprint and exit with status 1. - C: no exception system; the analog is checking return values and exiting via
exit(1). - C++:
int main()can let exceptions propagate;std::terminateis called on uncaught exceptions and the program exits. - C#:
static void Main(...)has nothrowskeyword (C# has no checked exceptions); uncaught exceptions print and exit.