Java Exceptions: Catching and Handling

Professor Hans Noodles
CodeGym
Published in
9 min readFeb 25, 2019

Hi! I hate to mention it, but a huge part of a programmer’s work is dealing with errors. Most often, his or her own. It turns out that there are no people who don’t make mistakes. And there are no such programs either.

Of course, when dealing with an error, the main thing is to understand its cause. And lots of things can cause bugs in a program.

At some point, Java’s creators asked themselves what should be done with the most likely programming errors? Entirely avoiding them isn’t realistic, programmers are capable of writing stuff you can’t even imagine. :)

So, we need to give the language a mechanism for working with errors. In other words, if there’s an error in your program, you need some sort of script for what to do next. What exactly should a program do when an error occurs?

Today we will get acquainted with this mechanism. It’s called “exceptions”.

What’s an exception?

An exception is an exceptional, unplanned situation that occurs while a program is running.

There are many exceptions. For example, you wrote code that reads text from a file, and displays the first line.

public class Main {   public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
}
}

But what if there is no such file!

The program will generate an exception: FileNotFoundException.

Output:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)

In Java, each exception is represented by a separate class.

All these exception classes derive from a common "ancestor"—the Throwable parent class.

An exception class's name usually concisely reflects why the exception occurred:

  • FileNotFoundException (the file wasn't found)
  • ArithmeticException (an exception occurred while performing a mathematical operation)
  • ArrayIndexOutOfBoundsException (the index is beyond the bounds of the array). For example, this exception occurs if you try to display position 23 of an array that has only 10 elements.

In all, Java has almost 400 such classes! Why so many? To make them more convenient for programmers to work with.

Imagine this: you write a program, and while it runs it generates an exception that looks like this:

Exception in thread "main"

Uhhhh. :/ That doesn’t help much. It’s not clear what the error means or where it came from. There’s no helpful information here.

But the large variety of exception classes in Java gives the programmer what matters most: the type of error and its probable cause (embedded in the class name).

It’s quite another thing to see

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)

It’s immediately clear what the problem might be and where to start digging to solve the problem!

Exceptions, like instances of any classes, are objects.

Catching and handling exceptions

Java has special blocks of code for working with exceptions: try, catch and finally.

Code where the programmer believes an exception may occur is placed in the try block. That doesn't mean that an exception will occur here. It means that it might occur here, and the programmer is aware of this possibility.

The type of error you expect to occur is placed in the catch block. This also contains all the code that should be executed if an exception occurs.

Here's an example:

public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
}
}

Output:

Error! File not found!

We put our code in two blocks. In the first block, we anticipate that a “File not found” error may occur. This is the try block.

In the second, we tell the program what to do if an error occurs. And the specific error type: FileNotFoundException. If we put a different exception class in the parentheses of the catch block, then FileNotFoundException won't be caught.

public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (ArithmeticException e) {
System.out.println("Error! File not found!");
}
}

Output:

Exception in thread “main” java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)

The code in the catch block didn't run, because we "configured" this block to catch ArithmeticException, and the code in the try block threw a different type: FileNotFoundException. We didn't write an code to handle FileNotFoundException, so the program displays the default information for FileNotFoundException.

Here you need to pay attention to three things.

Number one. Once an exception occurs on some line in the try block, the code that follows will not be executed. Execution of the program immediately "jumps" to the catch block.

For example:

public static void main(String[] args) {
try {
System.out.println("Divide by zero");
System.out.println(366/0);// This line of code will throw an exception
System.out.println("This");
System.out.println("code");
System.out.println("will not");
System.out.println("be");
System.out.println("executed!");
} catch (ArithmeticException e) { System.out.println("The program jumped to the catch block!");
System.out.println("Error! You can't divide by zero!");
}
}

Output:

Divide by zero 
The program jumped to the catch block!
Error! You can’t divide by zero!

On the second line of the try block, we attempt to divide by 0, resulting in an ArithmeticException.

Consequently, lines 3-9 of the try block won't be executed. As we said, the program immediately starts executing the catch block.

Number two. There can be several catch blocks. If the code in the try block might throw not one, but several different types of exceptions, you can write a catch block for each of them.

public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
System.out.println(366/0);
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!"); } catch (ArithmeticException e) { System.out.println("Error! Division by 0!"); }
}

In this example, we’ve written two catch blocks. If a FileNotFoundException occurs in the try block, then the first catch block will be executed.

If an ArithmeticException occurs, the second block will be executed.

You could write 50 catch blocks if you wanted to. Of course, it's better to not write code that could throw 50 different kinds of exceptions. :)

Third. How do you know which exceptions your code might throw? Well, you may be able to guess some of them, but it's impossible for you to keep everything in your head.

The Java compiler therefore knows the most common exceptions and the situations where they might occur.

For example, if you write code that the compiler knows might throw two types of exceptions, your code won't compile until you handle them. We'll see examples of this below.

Now some words about exception handling.

There are 2 ways to handle exceptions.

We've already encountered the first: the method can handle the exception itself in a catch() block.

There is a second option: the method can re-throw the exception up the call stack. What does that mean?

For example, we have a class with the same printFirstString() method, which reads a file and displays its first line:

public static void printFirstString(String filePath) {   BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}

At present, our code doesn’t compile, because it has unhandled exceptions.

In line 1, you specify the path to the file. The compiler knows that such code could easily produce a FileNotFoundException.

In line 3, you read the text from the file. This process could easily result in an IOException (input/output error).

Now the compiler says to you, "Dude, I won't approve this code and I won't compile it until you tell me what I should do if one of these exceptions occurs. And they could certainly happen based on the code you wrote!"

There's no way to get around it: you need to handle both!

We already know about the first exception handling method: we need to put our code in a try block and add two catch blocks:

public static void printFirstString(String filePath) {   try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error, file not found!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("File input/output error!");
e.printStackTrace();
}
}

But this isn’t the only option. We could simply throw the exception higher instead of writing error-handling code inside the method.

This is done using the keyword throws in the method declaration:

public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}

After the keyword throws, we indicate a comma-separated list of all the types of exceptions that the method might throw. Why?

Now, if someone wants to call the printFirstString() method in the program, he or she (not you) will have to implement exception handling.

For example, suppose that elsewhere in the program one of your colleagues wrote a method that calls your printFirstString() method:

public static void yourColleagueMethod() {   // Your colleague's method does something   //...and then calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}

We get an error! This code won’t compile!

We didn’t write exception-handling code in the printFirstString() method. As a result, this task now falls on the shoulders of those who use the method.

In other words, the methodWrittenByYourColleague() method now has the same 2 options: it must either use a try-catch block to handle both of the exceptions, or re-throw them.

public static void yourColleagueMethod() throws FileNotFoundException, IOException {
// The method does something
//...and then calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}

In the second case, the next method in the call stack — the one calling methodWrittenByYourColleague()—will have to handle the exceptions. That's why we call this "throwing or passing the exception up".

If you throw exceptions upward using the keyword throws, your code will compile. At this point, the compiler seems to be saying, "Okay, okay. Your code contains a bunch of potential exceptions, but I'll compile it. But we'll return to this conversation!"

And when you call any method that has unhandled exceptions, the compiler fulfills its promise and reminds you about them again. Finally, we'll talk about the finally block (sorry for the pun). This is the last part of the try-catch-finally exception handling triumvirate.

It is different in that it is executed no matter what happens in the program.

public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
e.printStackTrace();
} finally {
System.out.println ("And here's the finally block!");
}
}

In this example, the code inside the finally block will be executed in both cases.

If the code in the try block is runs in full without throwing any exceptions, the finally block will run in the end.

If the code inside the try block is interrupted by an exception and the program jumps to the catch block, the finally block will still run after the code inside the catch block.

Why is this necessary?

Its main purpose is to execute mandatory code: code that must be performed regardless of the circumstances.

For example, it often frees up some resources used by the program.

In our code, we open a stream to read information from the file and pass it to the BufferedReader object.

We must close our reader and release the resources. This must be done no matter what—when the program works as it should, and when it throws an exception.

The finally block is a very convenient place to do this:

public static void main(String[] args) throws IOException {   BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
System.out.println ("And here's the finally block!");
if (reader != null) {
reader.close();
}
}
}

Now we are certain that we’ll take care of the resources, regardless of what happens when the program is running. :)

That’s not all you need to know about exceptions.

Error handling is a super important topic in programming. Lots of articles are devoted to it.

In the next lesson, we’ll find out what types of exceptions there are and how to create your own exceptions. :) See you then!

First was published on CodeGym blog:

--

--