An exception is an unexpected event that occurs at runtime due to an error. Exceptions disrupt the normal flow of a program.
If we had this snippet of code, it would compile.
There would be no errors, there would be no warnings.
However, when we run this, we run into an error when the loop attempts to access index 3 of this array because there is no index 3.
So, we receive an ArrayIndexOutOfBoundsException
and the program would crash.
You can handle exceptions within your code so that the program doesn't crash and so that you can provide a meaningful message in case of an error.
To do so, you use a try/catch clause.
Let's look at an example.
I'm going to write a program that creates a new file and we're going to handle exceptions that may occur.
So here I have an ExceptionHandling
class.
Let's go ahead and make a call to a new method called createNewFile()
, and we'll make that method in the class.
package chapter13;
public class ExceptionHandling {
public static void main(String args[]){
createNewFile();
}
public static void createNewFile(){
}
}
We’re going to use the File
class in Java to create a new object.
We give it the path of the file
public static void createNewFile(){
File file = new File("resources/nonexistent.txt");
}
The File
class has a method called createNewFile
, so it can create this file if the “resources” directory exists, which it does not.
public static void createNewFile(){
File file = new File("resources/nonexistent.txt");
file.createNewFile();
}
Notice here, it's not compiling.
If we hover over here, we see that it says, “Unhandled exception”, and it gives us the exception name that is unhandled — IOException
.
In order to handle this exception, we can place this statement in a try/catch block.
We type the word try
and we follow that with curly braces, then we place the statement inside of the try
block.
public static void createNewFile(){
File file = new File("resources/nonexistent.txt");
try{
file.createNewFile();
}
}
We follow the try
block with a catch
block. Notice that we have a compilation error because we don't have the catch
block.
Inside of catch's parenthesis, we have to specify the exception that we're going to catch — createNewFile
has the potential of throwing an IOException
, so we want to catch what it's throwing.
So, we specify that, and we give this a name.
try{
file.createNewFile();
}
catch (IOException e){
}
In most programs people just called this “e” or “ex”, something really short.
After the catch
, we also put a set of curly braces, and this is a block of code.
So, what happens is Java is going to attempt to run this createNewFile
. If an exception happens to be thrown, then we have provided a catch
block. The code will come inside of here and execute everything in here.
We can maybe provide a print statement for our users.
catch (IOException e){
System.out.println("Directory does not exist.");
}
Additionally, when an exception is caught, that exception has a stack trace as well, which will provide information about the error and also the path that the code took before getting there.
So, you can print this out as well. If we do e
, which is the name of the exception, dot (.
), we see this printStackTrace()
.
There's also a getMessage()
that will show just the error message without the stack trace.
So, let's just choose the printStackTrace()
.
package chapter13;
public class ExceptionHandling {
public static void main(String args[]){
createNewFile();
}
public static void createNewFile(){
File file = new File("resources/nonexistent.txt");
try{
file.createNewFile();
}catch (Exception e){
System.out.println("Directory does not exist.");
e.printStackTrace();
}
}
}
And we can run this.
Notice our message was printed, “Directory does not exist.”, and then here is the stack trace. So, it tells us the path that it took, started from the main
method, went to our createNewFile
method, then went to the File.createNewFile
method, and so on until it reached the exception.
When handling exceptions, you can also use the superclass as a way to catch broader exceptions.
If we look at the Javadoc for the ArrayIndexOutOfBoundsException, we see that it inherits from a chain of other exceptions with a top level exception class being Exception
.
All exceptions inherit from the Exception
class.
Let's take a look at our example.
Here we're catching the IOException
because we know that that's what this is going to throw. However, let's say that this method threw more than one exception, or we weren't quite sure exactly what exception it was going to throw.
Then we can use polymorphism and instead of catching the IOException
, we can specify a superclass.
Knowing that Exception
is the top-most superclass for all exceptions, then we can place that here.
try{
file.createNewFile();
}catch (Exception e){
System.out.println("Directory does not exist.");
e.printStackTrace();
}
It doesn't have to be just Exception
, we can use any of the parents of that IOException
.
There can be multiple catch
clauses to handle different types of exceptions.
If the multiple catch
clauses contain related exceptions, the subclass’ catch
clause must appear first. Otherwise, it will never have the possibility of reaching other catch clauses.
Let's look at an example.
We're going to write a program that reads decimal numbers from a file. And then we're going to handle two types of exceptions that come from that program — the FileNotFoundException
and InputMismatchException
.
Let's comment out this one and we're going to make a call to a new one.
We'll say numbersExceptionHandling
, and we'll create this method.
public class ExceptionHandling {
public static void main(String args[]){
// createNewFile();
numbersExceptionHandling();
}
public static void numbersExceptionHandling(){
}
}
Now in this method I'm going to create a new file object and then I want to read from that file.
I'm going to use the Scanner
object, and instead of System.in
, we're going to use the file.
public static void numbersExceptionHandling(){
File file = new File("resources/numbers.txt");
Scanner fileReader = new Scanner(file);
}
So, we're no longer reading from the console, we're going to read from the file instead.
If we hover over here, we see we have an unhandled exception, the FileNotFoundException
.
So, let's place this in a try
.
try{
Scanner fileReader = new Scanner(file);
}catch(FileNotFoundException e){
}
Next, we want to read every line from the file, so we're going to use a while
loop. And we saw this in the last chapter with iterators — read the number and then print that number to the console.
try{
fileReader = new Scanner(file);
while(fileReader.hasNext()){
double num = fileReader.nextDouble();
System.out.println(num);
}
}catch(FileNotFoundException e){
}
Technically, we're okay. We weren’t required to put this nextDouble()
inside of a try
block, however there is a possibility that when we're reading from the file, if one of the lines in the file is not an actual double
then we would get an exception, an InputMismatchException
.
We can handle the case of the FileNotFoundException
and we can also add another catch
block for the InputMismatchException
.
try{
Scanner fileReader = new Scanner(file);
while(fileReader.hasNext()){
double num = fileReader.nextDouble();
System.out.println(num);
}
}catch(FileNotFoundException e){
}catch(InputMismatchException e){
}
So now, if either one of these exceptions is thrown it will go into the respective clause.
We haven't placed anything inside of these clauses yet. Let's say that all we wanted to do was print the stack trace, then we could add that in both clauses.
Let's say we also wanted to catch any other exception that occurs and so we add a 3rd catch block.
Notice now we have a compilation error because Exception
is a superclass to FileNotFoundException
and also to InputMismatchException
. So, if any Exception
is thrown, it's going to first check this one. It's going to catch this one. There's no way that it will ever get to the other two. So, it's unreachable.
Now, we can still include this one, but it would have to be at the end of these — which means if there is a FileNotFoundException
, it will go there, if there's an InputMismatch
, it will go there, and then finally if there's any other Exception
it will come here.
I'm going to get rid of this one. If we look at the two that we have, they're fine.
This is okay. It works. However, there's some redundancy here. All we are doing is printing the stack trace. So, it's kind of foolish to have both of these listed this way.
We can simplify this by adding all of the exceptions that we want to catch into one catch
block.
The way we do that is by using the pipe symbol: |
.
So, if we have FileNotFoundException
, we can use pipe and then we can specify any other exception we want.
catch(FileNotFoundException | InputMismatchException e){
e.printStackTrace();
}
You can add however many more exceptions you want, just separate them with this pipe symbol.
public class ExceptionHandling {
public static void main(String args[]){
numbersExceptionHandling();
}
public static void numbersExceptionHandling(){
File file = new File("resources/numbers.txt");
try{
Scanner fileReader = new Scanner(file);
while(fileReader.hasNext()){
double num = fileReader.nextDouble();
System.out.println(num);
}
}catch(FileNotFoundException | InputMismatchException e){
e.printStackTrace();
}
}
}
So, once we've done that, this will catch either one of these exceptions.
A finally
clause can optionally be added below any catch
clauses.
This clause is executed after try
and after any catch
clauses, even if the catch
clauses don't execute.
Let's revisit the previous example and add a finally
clause to close the file.
catch(FileNotFoundException | InputMismatchException e){
e.printStackTrace();
}finally{
fileReader.close();
}
After all of your catches, you can type in the word finally
. This one does not have a parenthesis, but it does have the curly braces. With this finally
block you put whatever else that you want to do.
Exceptions interrupt the flow of the program. Let's say for example, we have the exception and it was thrown right here when we tried to instantiate Scanner
.
Then it would come into this catch
block and would not execute the rest of this try
block. So, adding the scanner.close()
statement inside of the try
block is not a good idea because if this exception is thrown, we'll come to catch
block and this will never get executed.
The Finally Block
The finally
block says, "Hey, even if you finish everything in the try
, even if no exceptions are thrown, I will execute whatever you place inside of me." So, this will execute no matter what — if exceptions are thrown, or if exceptions are not thrown — finally
will execute.
So, anything that you just need to get done you, would place inside of the finally
block.
Now, we have an error because this is out of scope — remember, this fileReader
is defined inside of only the try
block.
We can fix this by declaring the fileReader
outside of the try
block. So, let's just do that and we'll initialize it inside the try
block and then the finally
block will close it.
public static void numbersExceptionHandling(){
File file = new File("resources/numbers.txt");
Scanner fileReader = null;
try{
fileReader = new Scanner(file);
while(fileReader.hasNext()){
double num = fileReader.nextDouble();
System.out.println(num);
}
}catch(FileNotFoundException | InputMismatchException e){
e.printStackTrace();
}finally{
fileReader.close();
}
}
That’s just one way to handle this. We could also handle this without using a finally
block.
There is an option that’s referred to as try with resources, which will allow you to add a parenthesis after the word try
and inside of the parentheses, you can initialize the resource there.
For example, the Scanner
object is a resource, so we can define it within these parenthesis, and get rid of the other places where we declared and initialized it.
File file = new File("resources/numbers.txt");
try(Scanner fileReader = new Scanner(file)){
while(fileReader.hasNext()){
double num = fileReader.nextDouble();
System.out.println(num);
}
}
Once we have the resource declared at this level, we no longer need the finally
clause to close the resource. Try with resources allows you to specify a resource and Java will automatically close this resource on your behalf once done with the try/catch.
This only works with classes that implement the Closable or AutoClosable interfaces, and Scanner
happens to be one of those.
So that’s an option when you’re working with resources and you were only going to use finally
to close the resource.
Otherwise, you may have other cases where finally
is a good approach to execute some other statements, and that’s fine as well.
So you have multiple options.