Java throw and throws — Creating and Declaring Exceptions
throw and throws are two sides of Java’s exception mechanism: throw creates and throws an exception; throws declares that a method might throw one.
throw — Throwing an Exception
Use throw to explicitly raise an exception:
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be 0–150, got: " + age);
}
this.age = age;
}
Syntax
throw new ExceptionType("message");
throwtakes an instance ofThrowable(or its subclass).- Execution stops immediately and jumps to the nearest matching
catch. - Code after
throwin the same block is unreachable.
Common Exceptions to Throw
| Exception | When to Use |
|---|---|
IllegalArgumentException |
Invalid method parameter |
IllegalStateException |
Object in wrong state for the operation |
NullPointerException |
Null where non-null is required |
UnsupportedOperationException |
Method not implemented |
IndexOutOfBoundsException |
Index outside valid range |
public void process(String data) {
if (data == null) {
throw new NullPointerException("data must not be null");
}
// Or, Java 1.7+:
Objects.requireNonNull(data, "data must not be null");
}
throws — Declaring Exceptions
Use throws in the method signature to declare that a method might throw a checked exception:
public String readFile(String path) throws IOException {
return Files.readString(Path.of(path));
}
Why Is This Required?
Java’s compiler enforces the handle-or-declare rule for checked exceptions:
- Handle it with
try-catch, OR - Declare it with
throws(pass the responsibility to the caller).
// Option 1: Handle it
public void loadConfig() {
try {
String content = readFile("config.yml");
} catch (IOException e) {
System.out.println("Config not found, using defaults");
}
}
// Option 2: Declare it
public void loadConfig() throws IOException {
String content = readFile("config.yml");
}
Multiple Exceptions
public void process() throws IOException, SQLException {
// may throw either
}
throw vs throws — Summary
| Feature | throw |
throws |
|---|---|---|
| Location | Inside method body | In method signature |
| Purpose | Throw an exception | Declare possible exceptions |
| What follows | An exception instance | Exception class name(s) |
| Required for | All exceptions (when you want to raise) | Checked exceptions only |
// throws (declaration)
// ↓
public void save() throws IOException {
if (!isValid()) {
throw new IOException("Invalid data");
// ↑
// throw (creation)
}
}
Rethrowing Exceptions
Catch an exception, do something, then rethrow:
public void process() throws IOException {
try {
readData();
} catch (IOException e) {
logger.error("Data read failed", e);
throw e; // rethrow the same exception
}
}
Wrapping and Rethrowing
Convert a low-level exception to a higher-level one:
public User findUser(long id) throws ServiceException {
try {
return database.query("SELECT * FROM users WHERE id = ?", id);
} catch (SQLException e) {
throw new ServiceException("Failed to find user " + id, e);
// cause ──┘
}
}
This preserves the cause chain — the original SQLException is accessible via getCause().
Chained Exceptions
Every exception can carry a cause — the underlying exception that triggered it:
try {
connectToDatabase();
} catch (SQLException original) {
RuntimeException wrapper = new RuntimeException("DB connection failed");
wrapper.initCause(original);
throw wrapper;
}
// Or, more commonly, use the constructor:
throw new RuntimeException("DB connection failed", original);
Accessing the Chain
catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
System.out.println("Caused by: " + e.getCause().getMessage());
e.printStackTrace(); // shows full chain
}
Stack trace output:
java.lang.RuntimeException: DB connection failed
at App.main(App.java:10)
Caused by: java.sql.SQLException: Connection refused
at Database.connect(Database.java:45)
... 2 more
When to throw vs When to Handle
| Situation | Action |
|---|---|
| Caller can recover | throws — let them decide |
| No meaningful recovery | throw unchecked exception |
| Library/API boundary | throw domain-specific exception |
| Internal implementation detail | Handle with try-catch |
| Precondition violated | throw IllegalArgumentException |
| Programmer error (bug) | throw unchecked (fail fast) |
The “Throw Early, Catch Late” Principle
Throw exceptions as soon as a problem is detected:
public void transfer(Account from, Account to, double amount) {
// Throw early — validate immediately
if (from == null) throw new NullPointerException("from account");
if (to == null) throw new NullPointerException("to account");
if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
// ... proceed with transfer
}
Catch exceptions at the level where you can handle them meaningfully:
// Controller layer — catch and return error response
try {
accountService.transfer(from, to, amount);
return Response.ok("Transfer successful");
} catch (InsufficientFundsException e) {
return Response.error(400, e.getMessage());
}
throws and Method Overriding
When overriding a method:
- You can throw fewer checked exceptions.
- You can throw narrower (subclass) checked exceptions.
- You cannot throw new or broader checked exceptions.
class Parent {
void read() throws IOException { }
}
class Child extends Parent {
// ✅ Narrower exception
void read() throws FileNotFoundException { }
// ✅ No exception at all
// void read() { }
// ❌ Broader exception — compile error
// void read() throws Exception { }
}
Practical Example — Input Validation
public class OrderService {
public Order createOrder(String customerId, List<Item> items)
throws InvalidOrderException {
if (customerId == null || customerId.isBlank()) {
throw new InvalidOrderException("Customer ID is required");
}
if (items == null || items.isEmpty()) {
throw new InvalidOrderException("At least one item is required");
}
for (Item item : items) {
if (item.getQuantity() <= 0) {
throw new InvalidOrderException(
"Invalid quantity for item: " + item.getName());
}
}
return new Order(customerId, items);
}
}
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
throw new Exception("...") |
Too generic | Throw specific types |
| Losing the cause | throw new X(msg) without cause |
Include original: new X(msg, e) |
throws Exception on every method |
Defeats purpose of checked exceptions | Declare specific types |
Throwing in finally |
Masks original exception | Avoid or use try-with-resources |
Not documenting throws |
Callers don’t know what to expect | Add @throws Javadoc |
Key Takeaways
throwcreates and throws an exception instance.throwsdeclares checked exceptions in the method signature.- Always include a meaningful message and the cause when wrapping.
- Follow “throw early, catch late” — validate inputs immediately, handle at the right level.
- When overriding, you can only throw same or narrower checked exceptions.
What’s Next?
Next: Checked vs Unchecked Exceptions — the fundamental distinction, the debate, and when to use each.