MediumException Handling in Java. Stop Memorizing Java Exceptions: A… | by Aditya Narayan Singh | Medium
Sitemap

Exception Handling in Java

5 min readJul 30, 2026

--

Stop Memorizing Java Exceptions: A Developer’s Mental Model

If you have spent any time writing Java, you have probably wrestled with exception handling. You’ve likely stared at a screen wondering, “Why is the compiler yelling at me to use a try-catch here?” or “Wait, do I use throw or throws in this method?"

Most developers try to memorize the rules. But to write rock-solid, production-level Java — the kind of code that senior engineers write — you don’t need to memorize anything. You just need to understand the “why.”

Here is the ultimate mental model for mastering Java Exceptions.

Phase 1: The Grand Divide (Checked vs. Unchecked)

The entire philosophy of Java exception handling comes down to one simple question: Who is to blame for the failure?

1. Unchecked Exceptions (The Logic Bugs) (or Developers mistake)

If the failure is the programmer’s fault, it is an Unchecked Exception (which extends RuntimeException).

These are logic bugs. Imagine an unchecked exception like leaving the front door open and your dog running out — it’s an unpredictable flaw in your setup. Common examples include NullPointerException, ArithmeticException (dividing by zero), or ArrayIndexOutOfBoundsException.

Because these are considered programming mistakes, the Java compiler does not check for them. It trusts that you wrote bulletproof code.

The Golden Rule: You generally shouldn’t use a try-catch for Unchecked Exceptions. You should fix your logic programmatically.

// BAD: Catching a logic bug
try {
System.out.println(myString.toUpperCase());
} catch (NullPointerException e) {
System.out.println("String is missing!");
}
// GOOD: Programmatic check
if (myString != null) {
System.out.println(myString.toUpperCase());
} else {
System.out.println("String is missing!");
}

2. Checked Exceptions (The External Forces)

If the failure is the outside world’s fault, it is a Checked Exception (which extends Exception).

Even if you write the most perfect algorithm in the universe, a database server can still go offline, an API can drop your connection, or a user might delete a file right before you try to read it. Because these failures are highly probable, the compiler forces you to handle them. It will aggressively refuse to compile your code until you prove you have a backup plan.

The “TOCTOU” Problem: Why try-catch is Mandatory

You might wonder: “If I can check for nulls with an if statement, why can't I just check if a file exists with if (file.exists())instead of using a try-catch?"

The answer is Time of Check to Time of Use (TOCTOU).

When you check your internal code (if myString != null), you have 100% control. But the outside world is chaotic. You might run if (file.exists()) and get true, but one millisecond later, an antivirus program locks the file. When your code immediately tries to read it, it explodes.

Get Aditya Narayan Singh’s stories in your inbox

Join Medium for free to get updates from this writer.

Because external state can change in a microsecond, an if statement isn't enough. Java forces you to wear your seatbelt: the try-catch.

Phase 2: The Defense Mechanisms

When dealing with the unpredictable outside world, we use the try-catch-finally structure.

  • try (The Hazardous Zone): You put dangerous operations here. If a bomb goes off, execution immediately jumps to the catch block.
  • catch (The Safety Net): You can have multiple catch blocks to handle different errors. Order matters. Java reads top-to-bottom, so always catch the most specific exceptions first, and fall back to general ones (like Exception) at the bottom.
  • finally (The Cleanup Crew): This block executes no matter what—even if an error is thrown, or if the try block has a return statement. If you open a database connection or acquire a lock in a multithreaded app, you must release it in the finally block to prevent deadlocks and memory leaks.

Phase 3: Passing the Buck vs. Sounding the Alarm

These two keywords sound identical, which is why they trip up almost every new Java developer. But they do two completely different jobs.

throws (Passing the Buck)

  • Location: In the method signature.
  • Meaning: “Warning to whoever is calling this method: I might explode with this specific Checked Exception. You need to write the try-catch block, because I'm not doing it."
public void connectToDB() throws SQLException {
// Dangerous DB connection code here...
}

throw (Sounding the Alarm)

  • Location: Inside the method body.
  • Meaning: “Stop everything! I am creating an exception right now and throwing it into the system!”
public void processArray(int[] nums) {
if (nums == null || nums.length == 0) {
throw new IllegalArgumentException("Array cannot be empty!");
}
}

Phase 4: Software Design Strategy (When to throw what)

Deciding whether to manually throw a Checked or Unchecked exception is a crucial software design choice. It dictates how other developers interact with your code.

When to throw an Unchecked Exception (The “Developer Error” Rule)

Throw an Unchecked Exception when the caller broke the rules (e.g., passing negative age, or an empty array).

  • The Message: “I am intentionally crashing the app right now so you notice your mistake and fix your code.”
  • The Action: They should NOT use a try-catch. They need to write a programmatic if check before ever calling your method.

When to throw a Checked Exception (The “Business Reality” Rule)

Throw a Checked Exception when the failure is nobody’s fault, but it is a predictable part of doing business (e.g., a user tries to withdraw $500 but only has $100).

  • The Message: “You passed me perfect data, but the business reality failed. I’m forcing you to write a backup plan.”
  • The Action: The compiler forces them to write a try-catch so they can gracefully show a warning banner on the UI instead of crashing the server.

Phase 5: The Expert Move (Custom Exceptions)

When working on production systems, generic exceptions aren’t enough. If a user tries to book a ride but another user beats them to it, throwing a generic RuntimeException results in a vague "Error" log.

Instead, build custom exceptions to make your code self-documenting:

// Extending Exception makes this a Checked Exception.
// Extending RuntimeException makes it Unchecked.
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}

Now, when you use throw new InsufficientFundsException("Wallet balance is too low"), your logs clearly explain exactly what went wrong, and other developers can catch that specific business case cleanly.

Summary

Stop memorizing syntax and start looking at exceptions as a way to communicate. Are you alerting a fellow developer of a logic bug (RuntimeException), or are you forcing them to build a safety net against the chaotic outside world (Exception)?

Once you understand the “why,” the code writes itself.

--

--