throw and throws in Java
throw raises an exception now; throws declares that a method may raise one and leaves the handling to the caller.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The difference in one table
throw | throws | |
|---|---|---|
| Purpose | Raises an exception | Declares what may be raised |
| Where | Inside a method body | In the method signature |
| Followed by | One exception object | One or more exception types |
| How many | One at a time | Any number, comma separated |
| Ends execution | Yes, immediately | No, it is only a declaration |
throw
public void setAge(int age) {
if (age < 0 || age > 130) {
throw new IllegalArgumentException("Age out of range: " + age);
}
this.age = age;
}Execution stops at the throw. Anything after it in the same block is unreachable and will not compile.
throws
public String readConfig(Path path) throws IOException {
return Files.readString(path); // the checked exception travels outwards
}throws is a declaration, not an action. It makes the possible failure part of the method contract, and the compiler then requires every caller to deal with it.
Handle or declare
// Option 1: handle it here
public String safeRead(Path path) {
try {
return Files.readString(path);
} catch (IOException e) {
return "";
}
}
// Option 2: declare it and let the caller decide
public String read(Path path) throws IOException {
return Files.readString(path);
}For a checked exception those are the only two options. Unchecked exceptions need neither, although declaring one in throws is legal and occasionally used as documentation.
Fail fast argument checking
public Order(String reference, int quantity) {
this.reference = Objects.requireNonNull(reference, "reference is required");
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity must be positive, was " + quantity);
}
this.quantity = quantity;
}Validate at the boundary and throw immediately. A failure at the point of the mistake is far cheaper to diagnose than a NullPointerException three layers away.
| Situation | Throw |
|---|---|
| An argument is invalid | IllegalArgumentException |
| An argument is unexpectedly null | NullPointerException, via Objects.requireNonNull |
| The object is in the wrong state for this call | IllegalStateException |
| An index is out of range | IndexOutOfBoundsException |
| An operation is not supported | UnsupportedOperationException |
Chaining the cause
try {
return jdbc.query(sql);
} catch (SQLException e) {
throw new DataAccessException("Query failed for note " + id, e); // cause kept
}DataAccessException: Query failed for note 42
at com.example.notes.NoteRepository.find(NoteRepository.java:58)
Caused by: java.sql.SQLException: connection closed
at ...The Caused by chain is what makes wrapping useful. Without it you keep the abstraction and lose the diagnosis.
throws and overriding
class Loader {
void load() throws IOException { }
}
class CachedLoader extends Loader {
@Override void load() throws FileNotFoundException { } // narrower, allowed
// @Override void load() throws Exception { } // broader, rejected
@Override void load() { } // none, allowed
}An override may declare the same checked exceptions, narrower ones, or none. It may never declare broader ones, because a caller holding the supertype has already written its handlers.
Rethrowing with precise types
public void run() throws IOException, SQLException {
try {
work(); // declares both
} catch (Exception e) {
logger.warning("failed");
throw e; // compiler knows only those two can occur
}
}Since Java 7 the compiler analyses what the try block can actually throw, so catching Exception and rethrowing does not force throws Exception on the signature.
Common mistakes
- Writing
throwswherethrowwas meant, or the reverse. - Declaring
throws Exceptionon everything, which tells callers nothing and forces them to catch too much. - Throwing a bare
new Exception("...")instead of a specific type. - Losing the cause when wrapping.
- Putting code after a
throwin the same block. - Declaring checked exceptions that the method cannot actually throw.
Best practices
- Throw the most specific standard exception that fits before inventing one.
- Put the offending value in the message.
- Declare only what a method can really throw, and keep the list short.
- Always pass the cause when wrapping.
- Validate arguments at the start of a method, not in the middle.
Practice
- Write a method that throws
IllegalStateExceptionwhen called before initialisation. - Why can an overriding method not declare a broader checked exception?
- Convert a method that returns
nullon failure into one that throws, and say what improves. - Wrap a low level exception in a domain one, keeping the cause, and read the resulting trace.
- Explain why
throws Exceptionon a public API is poor design.
Conclusion
throw raises, throws declares. Fail fast with specific types, put useful detail in the message, keep the cause when wrapping, and declare only what a method genuinely produces.