Your code may not always do what you expect it to do. Maybe you got some sort of invalid input data, maybe some upstream code errored in a way that cascaded down to us, or maybe we came across some edge case that we simply hadn’t initially accounted for.
In Java these sorts of errors are represented by an Exception. They are an “exception” to the standard control flow of our code.
What do they look like?
Let’s take a quick demonstration of what an exception may look like, using one on of the most common types of Exceptions in Java as an example.
Let’s say that we had some function to check if a Quarterback was elite
private static boolean isQuarterbackElite(String quarterbackName) {
return quarterbackName.equals("Joe Flacco")
|| quarterbackName.equals("Lamar Jackson");
}
Straightforward enough right?
But what happens if somebody calls our function and passes null as a parameter? You will receive something like this
Here we see something called a “stack trace” showing us where in the program this happened. In our case we can see that we have received a “NullPointerException” from our isQuarterbackElite function.
A NullPointerException occurs from trying to use a null value. In our case here the “quarterbackName” variable would be null, but we are then are trying to call the .equals() on it. We cannot determine if it is equal to Joe Flacco or Lamar Jackson, because there is nothing there.
In order to address this possibility, we could include a “null check” within our function
private static boolean isQuarterbackElite(String quarterbackName) {
return quarterbackName != null
&& (quarterbackName.equals("Joe Flacco")
|| quarterbackName.equals("Lamar Jackson"));
}
Now we can avoid having our program grind to a halt if we receive a null value.
Checking your Exceptions with Try Catch
The NullPointerException we just discussed is an example of a “runtime” exception. This means that the exception happens at runtime, in this case trying to dereference a null pointer.
The counterpart to runtime exceptions are “checked” exceptions. These are exceptions that can be detected at compile, and Java will ask you to specify what you want your code to do in the event that you catch that exception.
You may remember from our Input / Output post that we encountered this trying to read and write from a file. Let’s say we had some code that could read in a list of quarterbacks and determine if they were elite or not.
BufferedReader reader = new BufferedReader(new FileReader("resources/eliteQuarterbacks.txt"));
for (String name = reader.readLine(); name != null; name = reader.readLine()) {
if (isQuarterbackElite(name)) {
System.out.println(name + " is an ELITE Quarterback!");
} else {
System.out.println(name + " is NOT an ELITE Quarterback!");
}
}
You may notice very quickly that Java is not happy about our “Unhandled Exceptions.” IOExceptions, and its subtypes such as FileNotFoundException, are checked exception so we must deal with them now.
We can do this by using something called a try catch block.
try {
BufferedReader reader = new BufferedReader(new FileReader("resources/eliteQuarterbacks.txt"));
for (String name = reader.readLine(); name != null; name = reader.readLine()) {
if (isQuarterbackElite(name)) {
System.out.println(name + " is an ELITE Quarterback!");
} else {
System.out.println(name + " is NOT an ELITE Quarterback!");
}
}
reader.close();
} catch (IOException e) {
System.out.println("There has been some issue proving the eliteness of quarterbacks.");
e.printStackTrace();
}
We have put our block of code in the “try” block, and we have a “catch” block for IOException’s where we will print a straightforward message that we encountered as well as printing the stack trace from our exception object.
Closing your Resources
There is however another thing to consider here. If we were to catch an IOException, the line to close our reader wouldn’t execute, leaving ourselves open to a memory leak. In that case we can do something like this
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("resources/eliteQuarterbacks.txt"));
for (String name = reader.readLine(); name != null; name = reader.readLine()) {
if (isQuarterbackElite(name)) {
System.out.println(name + " is an ELITE Quarterback!");
} else {
System.out.println(name + " is NOT an ELITE Quarterback!");
}
}
} catch (IOException e) {
System.out.println("There has been some issue proving the eliteness of quarterbacks.");
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
System.out.println("We were unable to close our reader.");
e.printStackTrace();
}
}
The “finally” block is guaranteed to run regardless of how our code exits from the try catch, so we can ensure that our reader does not leak. Since the close method itself also requires us to catch IOException we also have a mini try catch block within our finally block as well. In order to avoid that, we can use something called a “try with resources” that was introduced in Java 8.
try (BufferedReader reader = new BufferedReader(new FileReader("resources/eliteQuarterbacks.txt"))) {
for (String name = reader.readLine(); name != null; name = reader.readLine()) {
if (isQuarterbackElite(name)) {
System.out.println(name + " is an ELITE Quarterback!");
} else {
System.out.println(name + " is NOT an ELITE Quarterback!");
}
}
} catch (IOException e) {
System.out.println("There has been some issue proving the eliteness of quarterbacks.");
e.printStackTrace();
}
Now we can be sure that our file reader will be closed at the end of the block.
Putting it Together
If we were to run this we with an input file of:
Joe Flacco
Lamar Jackson
Ben Roethlisberger
We should see the output of:
Joe Flacco is an ELITE Quarterback!
Lamar Jackson is an ELITE Quarterback!
Ben Roethlisberger is NOT an ELITE Quarterback!
Just to check our own exception handling, let’s say we tried to look for a different file that wasn’t present. Perhaps something like:
try (BufferedReader reader = new BufferedReader(new FileReader("resources/proofBenRoethlisbergerIsReallyElite.txt"))) {
We should see this output:
There has been some issue proving the eliteness of quarterbacks.
java.io.FileNotFoundException: resources\proofBenRoethlisbergerIsReallyElite.txt (The system cannot find the file specified)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
at java.base/java.io.FileReader.<init>(FileReader.java:60)
at code.to.live.by.ExceptionHandling.main(ExceptionHandling.java:20)
Exceptions vs Errors
There is another category of Throwable‘s in Java that we should address. We have so far been talking about Exception’s but there is another category of Error‘s. These are typically things that we cannot recover from, such as the OutOfMemoryError and the StackOverflowError, so these not included within try catch blocks. However, it is still important to be aware of them.