Java Exception Handling
As I moved into my first projects with exception handling I had a conceptual problem with when I should be using throws
and when I should be using a try/catch
block. Then I had the full “Ohhhhhhhhhhhhh” moment while I was reading the chapter on exception handling in “Head First Java”.
Here is the TL;DR right up front:
- If a method is considered risky (it might not execute properly due to exceptional circumstances) it must declare that it throws an exception.
- The risky method that is called will
throw
the exception. - The method that calls the risky method will
catch
the exception. - The files are in the computer…
Checked Mate
There are two main categories of exceptions that you will run into in Java, checked and unchecked.
- A checked exception simply means that it is checked by the compiler. If there is an unaddressed checked exception it will prevent your program from making it past the compiler.
IOExceptions
throw checked exceptions and if you want to build software that is capable of networking you will need to learn to deal with them. - An unchecked exception will slide past the compiler and you won’t be notified of the issue until the program breaks down in mid execution. They will fall under the category of
RuntimeExceptions
.The most common (and kryptonite of every beginning Java programmer) is the dreadedNullPointerException
. Another common, and arguably easier to diagnose is the divide by zero exception (/ by zero
).
Why checked and unchecked? Checked exceptions are created by exceptional situations and are addressed by try/catch
blocks that come with their overhead and issues. Unchecked exceptions are created by issues in your code that you have created and need to be addressed during the development phase of your program and not by the additional overhead of a try/catch
block.
Here is an example of a / by zero
that is unchecked and won’t trigger until runtime:
public class DivideByZero {
int dividerOfZeros(int notZero) {
return notZero / 0;
} public static void main(String[] args) {
DivideByZero dbz = new DivideByZero();
dbz.dividerOfZeros(10);
}
}
The class DivideByZero()
has one method, dividingByZero()
that takes a single integer and divides it by zero. You can compile this class and it will run. You should see something similar in your terminal:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at DivideByZero.dividerOfZeros(DivideByZero.java:3)
at DivideByZero.main(DivideByZero.java:8)
The program runs up until it hits the unchecked exception, in this case, an ArithmeticException
created by dividing by zero. To prove that the program ran before this you could add some additional functionality to it:
public class DivideByZero {
int dividerOfZeros(int notZero) {
return notZero / 0;
} public static void main(String[] args) {
System.out.println("Hello from the beginning.");
DivideByZero dbz = new DivideByZero();
dbz.dividerOfZeros(10);
System.out.println("Hello from the end.");
}
}
This should give you the following in your terminal:
Hello from the beginning.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at DivideByZero.dividerOfZeros(DivideByZero.java:3)
at DivideByZero.main(DivideByZero.java:9)
The first print statement was hit and then we attempt to divide by zero and bork the rest of the program. If we were to attempt the same thing with a checked exception we would never get past the compiler to get to a point where the program runs:
import java.net.ServerSocket;
public class ReadAlong {
ServerSocket socket;
void createSocket() {
System.out.println("One socket, coming right up.");
socket = new ServerSocket(5000);
System.out.println("How'd you like that socket?");
}
}
We would get the following error:
ReadAlong.java:7: error: unreported exception IOException; must be caught or declared to be thrown
socket = new ServerSocket(5000);
^
1 error
error: compilation failed
Notice the error: compilation failed at the bottom of the terminal. The other thing that stands out is that we never saw our first print statement in the terminal confirming that the compiler never tried. If you are using a modern IDE to program with Java it will generally make an effort to save you from yourself when dealing with these errors. It doesn’t show with Medium’s monochromatic code block formatting, but IntelliJ through up splashes of red all over my monitor while I was writing the failing code.
Modern IDE’s will warn you that you are dealing with an unhandled exception when you are dealing with a risky method, the help window even went as far as to tell me what type of exception I’m dealing with (IOException
). If your curiosity is getting the better of you, you can start digging into the classes that make up the java.net
library. If we take a look at the java.net.ServerSocket
class we will see the following:
public ServerSocket(int port) throws IOException {
this(port, 50, (InetAddress)null);
}
If we refer back to excellent TL;DR at the top of this blog post we will recall that a risky method must declare that it throws an exception. We now know that if risky code is being called (ServerSocket
) the code calling it (ReadAlong.createSocket()
) must be ready to catch that exception.
So let’s add a try/catch
block to our code:
public class ReadAlong {
ServerSocket socket;
void createSocket() {
System.out.println("One socket, coming right up.");
try {
socket = new ServerSocket(5000);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("How'd you like that socket?");
}
}
All of the errors are now resolved and the class can be compiled without further issue.
Duck, Duck, Catch
(unless you live in the state of Minnesota, then it’s Duck, Duck, Gray Catch)
Before I made the mental connection of playing catch with using try/catch
blocks I struggled with throwing and catching. What made it worse was that IntelliJ would offer me the ability to use either to make the very bad and very mean squiggly red lines underneath my code go away.
So when do I surround my code with a try/catch
and when do I add an exception to my method signature? Enter ducking. Think about the catch metaphor again. What if we don’t want the exception to be handled in ReadAlong
because it’s not the purpose of that class? We can add an exception to its method signature as well, so we end up with a chain of throws:
public class ReadAlong {
ServerSocket socket;
void createSocket() throws IOException {
System.out.println("One socket, coming right up.");
socket = new ServerSocket(5000);
System.out.println("How'd you like that socket?");
}
}
With exception handling, someone eventually has to catch the exception that is being thrown. In this case, the class that is calling ReadAlong
will catch it:
public class ISeeWhatYouDidThere {
public static void main(String[] args) {
ReadAlong readAlong = new ReadAlong();
readAlong.createSocket();
}
}
Once again our program won’t compile. Now the issue is located in line four of our class. Let’s fix it:
import java.io.IOException;
public class ISeeWhatYouDidThere {
public static void main(String[] args) {
ReadAlong readAlong = new ReadAlong();
try {
readAlong.createSocket();
} catch (IOException e) {
e.printStackTrace();
}
}
}
We are back in business. When organizing your exception handling remember that:
- You can have multiple throws exception signatures.
- You will need one class that eventually takes responsibility for catching the thrown exception.
- You can use the Single Responsibility Principle (SRP) to help determine which class should
try/catch
the exception.
Anatomy of a Try
The basic building blocks of atry/catch
block are:
try
— This is the code that you are attempting to executecatch
— This code will execute if your try fails, it is typically used to help pass along information related to why the code in yourtry
statement failed.finally
— This code will run regardless of the outcome of yourtry/catch
statement.
try {
//Try this code right here, I hope it works.
} catch (Exception ex) {
//If that code above doesn't work run this code right here
}
- You can have multiple catch statements
try {
//to eat something spicy
} catch (MouthBurnsException mbex) {
//let them know your mouth hurts
} catch (TummyHurtsException thex) {
//let them know your tummy hurts
}
- You canNOT insert additional code between the
try
and yourcatches
.
try {
//Try this code right here, I hope it works.
}
string codeBetweenMyTryAndCatch = "not today"
catch (Exception ex) {
//If that code up above doesn't work run this code right here
}
When we want to run a block of code that is not dependent on the success of the try/catch
we will use a finally
statement.
try {
//Try this code right here, I hope it works.
} catch (Exception ex) {
//If that code above doesn't work run this code right here
} finally {
//It doesn't matter if the try code worked or not, I'm going to be executed.
All Your Ducks In a Row
Due to the polymorphic nature of exceptions you need to pay attention to the order that you catch them in. Let’s use an example of a zoo that has problems with animals escaping from their pens. Everything that can escape from the zoo is an animal:
try {
//is animal present
} catch (MissingAnimalException maex) {
//oh no an animal has gone missing
}
MissingAnimalException
is a very generic exception. Is there anything else that we might want to identify while we are tackling the problem? How about if the missing animal is dangerous? Large? Poisonous? Made of tiger?
When we create our own exceptions we will be extending
the RuntimeExceptionClass
. This means that our exceptions will be unchecked and inheriting properties from its parent class. So let’s build out some animal exceptions for our try/catch
statement:
//Exceptions:
public class MissingAnimalException extends RuntimeException {
}public class DangerousAnimalExcep extends MissingAnimalException {
}public class TigerException extends DangerousAnimalExcep {
}//Animals:
public class Turtle implements Animal {
public void inPen() throws MissingAnimalException {}
}public class Tiger implements Animal {
public void inPen() throws TigerException {}
}
When we create our try/catch
block we need to start with the smallest (narrowest of scope) exception and work from there. If our first catch was MissingAnimalException, every exception that extends it would be caught within it and we would miss out on more specific information that our zookeepers would be very interested in:
try {
animalInPen();
} catch (MissingAnimalException maex) {
System.error.println("Animal escape, remain calm.");
} catch (DangerousAnimalExcep daex) {
System.error.println("Dangerous animal has escaped, seek shelter.");
} catch (TigerException tex) {
System.error.println("Tiger escape, laser pointers activated, catnip deployed, seek shelter");
}
With the above example, the zookeepers can’t tell that the animal is a turtle or a dangerous tiger because the exceptions were caught by the largest exception being thrown first. Once the MissingAnimalException catches the exception, the program stops looking and moves on. If we reverse this order we would see a different exception thrown for our turtle (MissingAnimalException
) and our tiger (TigerException
):
try {
animalInPen();
} catch (TigerException tex) {
System.error.println("Tiger escape, laser pointers activated, catnip deployed, seek shelter");
} catch (DangerousAnimalExcep daex) {
System.error.println("Dangerous animal has escaped, seek shelter.");
} catch (MissingAnimalException maex) {
System.error.println("Animal escape, remain calm.");
}
I’ve covered some of the main problems I ran into while learning about exception handling in Java. I hope this will save you some time and I’d love to answer any questions that I can in the comments below.