Use Exception for problems your program can handle, and treat Error as a giant red alarm from the JVM. Both come from Throwable, but they mean very different things. If you catch the wrong one, your app may hide a serious failure and make debugging feel like stepping on Lego.
TLDR: Throwable is the parent type for anything Java can throw. Exception is for problems your code may recover from, like a missing file or bad input. Error is for serious JVM-level trouble, like OutOfMemoryError, and you usually should not catch it. For example, in a small API handling 10,000 requests per day, you may log and handle 300 normal exceptions, but even one memory error needs urgent attention.
The family tree
Java exception handling starts with one class: Throwable.
That name is literal. If something can be used with throw, it must be a Throwable or one of its children.
The tree looks like this:
Throwableis the root.Exceptionis for recoverable problems.Erroris for serious system problems.
Think of it like a hospital.
Exceptionis a broken arm. Bad, but fixable.Erroris the building catching fire. Stop pretending the appointment can continue.Throwableis the full category of “something went wrong.”
What is Throwable?
Throwable is the top class for Java problems.
It gives Java errors and exceptions useful features:
- A message, such as
"File not found". - A cause, which points to the deeper problem.
- A stack trace, which shows where the problem happened.
When you see a stack trace in the console, you are seeing Throwable doing its job. It is the “crime scene report” of Java.
Example:
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
This prints details that help you find the bug. Annoyingly, the stack trace can be long. Still, it is better than guessing for 20 minutes while your coffee gets cold.
What is an Exception?
An Exception means something went wrong, but the program may still recover.
Common examples include:
IOException: a file or network problem.SQLException: a database problem.NumberFormatException: text could not become a number.NullPointerException: you usednulllike it had a value.
Exceptions split into two main groups.
Checked exceptions
A checked exception must be handled or declared.
The compiler checks this. It will not let you ignore the problem.
public void readFile() throws IOException {
Files.readString(Path.of("data.txt"));
}
If you call this method, Java says, “Hey, files can fail. Deal with it.”
Checked exceptions are common with files, databases, and network calls.
Unchecked exceptions
An unchecked exception does not need to be declared.
These usually extend RuntimeException.
String name = null;
System.out.println(name.length());
This throws NullPointerException.
The compiler does not stop you. Runtime does. Rude, but fair.
Unchecked exceptions often mean a bug in your code. Bad input can cause them too.
What is an Error?
An Error means the JVM has a serious problem.
Your application usually cannot fix it.
Common examples include:
OutOfMemoryError: the JVM ran out of memory.StackOverflowError: recursive calls went too deep.NoClassDefFoundError: a needed class is missing at runtime.VirtualMachineError: the JVM itself is in trouble.
You should almost never catch Error.
Why? Because the app may already be broken in a deep way. Trying to continue can make things worse.
Imagine your app runs out of memory, then your catch block tries to build a huge error report. Great. Now the fire is wearing a tiny hat.
Throwable vs Exception vs Error
Here is the simple version:
Throwable: The parent of both exceptions and errors.Exception: A problem the program may handle.Error: A serious problem the program should usually not handle.
Use Exception in normal application code.
Avoid catching Throwable. It catches everything, including Error. That sounds useful until it hides a crash your team really needed to see.
Bad idea:
try {
runApp();
} catch (Throwable t) {
System.out.println("Something failed");
}
This catches OutOfMemoryError, StackOverflowError, and other scary things. It may keep the program limping along like a shopping cart with one cursed wheel.
Better:
try {
runApp();
} catch (Exception e) {
logger.error("Application error", e);
}
This handles normal app failures. It lets major system failures stay loud.
When should you catch Exception?
Catching Exception is useful at system borders.
Good places include:
- A web controller.
- A background job runner.
- A message queue consumer.
- A command line app entry point.
At these spots, you can log the issue and return a clean response.
Example:
try {
orderService.placeOrder(request);
} catch (Exception e) {
logger.error("Order failed", e);
return "Sorry, your order could not be placed.";
}
This is fine. The user gets a friendly message. The logs keep the ugly details.
Inside business logic, prefer specific exceptions.
try {
payment.charge(card);
} catch (PaymentDeclinedException e) {
showMessage("Card declined.");
}
This is clearer. It says exactly what went wrong.
When should you throw your own exception?
Create custom exceptions when they make code easier to read.
For example:
public class InvalidOrderException extends Exception {
public InvalidOrderException(String message) {
super(message);
}
}
Now your code can say what it means:
if (order.isEmpty()) {
throw new InvalidOrderException("Order has no items.");
}
That is better than throwing a plain Exception. Plain exceptions are vague. Vague errors waste time.
Should you ever catch Throwable?
Rarely.
Some frameworks catch Throwable at the very top level. Test runners may do it. App servers may do it. Logging wrappers may do it before shutting down.
That is special plumbing code. Normal feature code should not do it.
If you catch Throwable, ask yourself one question:
“Am I about to safely stop the app or isolate this task?”
If the answer is no, catch a smaller type.
Best practices that save headaches
- Catch specific exceptions first. Use
IOExceptionbeforeException. - Do not swallow exceptions. Empty catch blocks are tiny bug caves.
- Log the full exception. Keep the stack trace.
- Do not catch
Errorunless you are writing low-level framework code. - Wrap exceptions with context. Add useful messages.
- Fail clearly. A loud failure beats a silent mess.
Here is a solid pattern:
try {
reportService.generateMonthlyReport();
} catch (IOException e) {
throw new ReportException("Could not read report data.", e);
}
The original cause is preserved. The new message adds meaning. Future you will be grateful.
The simple mental model
Use this rule:
- Can my code recover? Use or catch an
Exception. - Is the JVM or environment breaking? Let the
Errorrise. - Do I need the root type? Almost never use
Throwabledirectly.
Throwable is the big umbrella. Exception is the bad weather you can handle with a jacket. Error is a tornado. Do not fight the tornado with a catch block.
Keep your catches narrow. Keep your logs useful. Let serious failures be serious. Java will still be Java, but at least the chaos will have labels.

