Things go wrong: a file is missing, a number is badly formatted, an API times out. Java signals these with exceptions — objects that describe the problem and travel up the call stack until something handles them. Good exception handling is what separates a script from a reliable integration.
try / catch / finally
try {
int q = Integer.parseInt("abc"); // throws NumberFormatException
System.out.println(q);
} catch (NumberFormatException e) {
System.out.println("Not a number: " + e.getMessage());
} finally {
System.out.println("Always runs"); // cleanup, logging
}
When an exception is thrown, Java jumps to the first matching catch. The finally block runs whether or not there was an error.
Checked vs unchecked exceptions
| Checked | Unchecked | |
|---|---|---|
| Examples | IOException, SQLException | NullPointerException, IllegalArgumentException |
| Extends | Exception | RuntimeException |
| Compiler forces you to handle or declare it? | yes | no |
| Typical meaning | something outside your control failed | a bug or invalid input |
For checked exceptions, either catch them or declare them with throws:
static int parseQuantity(String text) throws InvalidOrderException { ... }
Throwing your own
if (q <= 0) {
throw new InvalidOrderException("Quantity must be positive: " + q);
}
A custom exception with a domain name (InvalidOrderException, PartnerRejectedException) makes logs and error reports far clearer than a generic Exception:
static class InvalidOrderException extends Exception {
InvalidOrderException(String message) { super(message); }
}
Wrapping: keep the cause
When you catch a low-level exception and throw a more meaningful one, attach the original as the cause so the stack trace still shows what really happened:
catch (NumberFormatException e) {
throw new IllegalArgumentException("Bad quantity '" + text + "'", e);
}
try-with-resources
Files, readers, database connections and HTTP clients must be closed. try-with-resources closes them automatically — even when an exception is thrown:
try (BufferedReader reader = new BufferedReader(new FileReader("orders.csv"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Could not read: " + e.getMessage());
}
Any class that implements AutoCloseable works here.
Habits that pay off
- Catch what you can handle, at the level that knows what to do. Don’t catch everything everywhere.
- Never swallow exceptions with an empty
catch {}— at minimum, log them. - Fail fast on invalid input with
IllegalArgumentException; validate in constructors. - Include context in messages: which order, which file, which value.
The full example
// Exceptions.java — try/catch/finally, try-with-resources and custom exceptions.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class Exceptions {
// A custom checked exception for our domain
static class InvalidOrderException extends Exception {
InvalidOrderException(String message) { super(message); }
}
static int parseQuantity(String text) throws InvalidOrderException {
try {
int q = Integer.parseInt(text.trim());
if (q <= 0) {
throw new InvalidOrderException("Quantity must be positive: " + q);
}
return q;
} catch (NumberFormatException e) {
// Wrap the low-level error in a meaningful one, keeping the cause
InvalidOrderException ex = new InvalidOrderException("Not a number: '" + text + "'");
ex.initCause(e);
throw ex;
}
}
public static void main(String[] args) {
for (String input : new String[] {"24", "-3", "abc"}) {
try {
System.out.println("Quantity: " + parseQuantity(input));
} catch (InvalidOrderException e) {
System.out.println("Rejected: " + e.getMessage());
} finally {
System.out.println(" (checked '" + input + "')");
}
}
// try-with-resources closes the reader automatically, even on errors
String csv = "id,qty\n1,24\n2,10";
try (BufferedReader reader = new BufferedReader(new StringReader(csv))) {
reader.readLine(); // skip the header
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Row -> " + line);
}
} catch (IOException e) {
System.out.println("Could not read: " + e.getMessage());
}
}
}
Try it yourself
Write a method double safeDivide(double a, double b) that throws an IllegalArgumentException with a clear message when b is zero, and call it inside a try/catch that prints the message.
Show a solution
static double safeDivide(double a, double b) {
if (b == 0) {
throw new IllegalArgumentException("Cannot divide " + a + " by zero");
}
return a / b;
}
try {
System.out.println(safeDivide(10, 0));
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
Key takeaways
try/catch/finallyhandles errors;finallyalways runs.- Checked exceptions must be caught or declared; unchecked ones usually signal bugs.
- Use custom exceptions with clear names, and keep the original cause when wrapping.
- Use try-with-resources for anything that must be closed.