In Java, an exception is essentially a problem that pops up while your program is running. Picture yourself driving and suddenly hitting a roadblock — you can't just keep going like nothing happened; you need to deal with it before continuing your journey.
Exceptions work the same way in code. They're Java's way of tapping you on the shoulder and saying, "hey, something's wrong here, and it needs your attention."
The Three Types of Java Exceptions
Java exceptions generally fall into three buckets: checked exceptions, unchecked exceptions, and errors.
Checked Exceptions
These are the ones the compiler won't let you ignore. If your code could potentially throw one, Java forces you to handle it — either by catching it or explicitly passing the responsibility along. A classic example is IOException, which shows up when something goes wrong during a file operation.
Unchecked Exceptions
These surface at runtime instead of compile time, and the compiler doesn't force you to handle them. NullPointerException and ArithmeticException are two common examples. They usually trace back to bugs in your own logic — bad assumptions, missing checks, that sort of thing.
Errors
Errors are a different beast entirely. They represent serious problems that are usually outside your application's ability to recover from, like running out of memory. Rather than something you catch and handle gracefully in your code, errors typically point to a bigger issue at the system level.
Why Bother Handling Exceptions?
A few solid reasons:
- They push you toward better coding habits by forcing you to think about what could go wrong before it actually does.
- They keep your application from crashing outright — instead of dying unexpectedly, your program can recover, log the issue, or exit gracefully.
- They give you a clear trail of what went wrong, which makes debugging a lot less painful.
Handling Exceptions in Practice
The Try-Catch Block
This is your main tool for handling exceptions. You wrap the risky code in a try block, and if something goes wrong, the catch block steps in to handle it.
try {
int result = 10 / 0; // this will throw ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
}
The try block holds the code that might blow up, and the catch block catches the specific exception you're watching for — in this case, ArithmeticException — and responds instead of letting the program crash.
Adding a Finally Block
Sometimes you need certain code to run no matter what happens — whether an exception occurred or not. That's what finally is for, and it's commonly used for cleanup work like closing files or releasing resources.
try {
int result = 10 / 0; // potential exception
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
} finally {
System.out.println("Cleanup complete");
}
Here, "Cleanup complete" prints every single time — exception or not.
Throwing Your Own Exceptions
Sometimes you want to trigger an exception deliberately, usually to enforce some rule in your code. That's where the throw keyword comes in:
public void checkAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("Age must be at least 18");
}
}
If someone calls this method with an age under 18, it throws an IllegalArgumentException on the spot, flagging that something violates the expected rules.
Passing the Buck with Throws
Sometimes a method doesn't want to deal with an exception itself — it'd rather let whoever calls it decide how to handle it. That's what the throws keyword in a method signature does:
public void readFile(String fileName) throws IOException {
FileReader file = new FileReader(fileName);
}
This tells anyone calling readFile upfront: "heads up, this might throw an IOException, so you'll need to handle it."
A Few Common Exceptions You'll Run Into
NullPointerException
Happens when your code tries to use something that's null as if it were a real object — like trying to start a car without a key in the ignition.
String name = null;
System.out.println(name.length()); // throws NullPointerException
ArrayIndexOutOfBoundsException Occurs when you try to access an array position that doesn't exist — reaching past the edges of the sandbox, so to speak.
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // throws ArrayIndexOutOfBoundsException
ClassCastException Comes up when you try to cast an object into a type it isn't actually compatible with — like trying to force a square peg into a round hole.
Object obj = "Hello";
Integer number = (Integer) obj; // throws ClassCastException