The exception handling is a mechanism to handle runtime errors (exceptions) so that normal flow of the application execution can be maintained. To handle exceptions of any types Java provides a set of special keywords: try, catch and finally.
After a method throws an exception, the runtime system attems to find something to handle it (a suitable handler). A handler may be in the same method where the exception occured or in the calling method.
Do not forget, exceptions are divided into two groups: checked and unchecked. The exception handling mechanism can process both these groups.
The try-catch statement
Here is the simplest template for handling exceptions:
try {
// a code throwing an exception
} catch (Exception e) {
// a code for handling exception
}
Inside the try block, you write a code where an exception may occur. Inside the catch block, you write a code for handling exceptions with the specified type. The catch block contains a code that is executed if the exception occurs in the corresponding try block.
The argument type declares the type of exception that the catch block can handle. It must be the name of a class that extends the Throwable class.
In the presented template all exceptions of the class Exception and all its subclasses will be caught by the catch block including all checked and unchecked exceptions.
See the following example to better understand the order of blocks execution:
System.out.println("before the try-catch block"); // it will be printed
try {
System.out.println("inside the try block before an exception occurs"); // it will be printed
int[] numbers = { 1, 2, 3 };
numbers[3] = 4; // ArrayIndexOutOfBoundsException occurs here
System.out.println("inside the try block after the exception occurs"); // it won't be printed
} catch (Exception e) {
System.out.println("inside the catch block"); // it will be printed
}
System.out.println("after the try-catch block"); // it will be printed
The output:
before the try-catch block
inside the try block before an exception occurs
inside the catch block
after the try-catch block
As you can see, the text "inside the try block after the exception occurs" is not printed, because the exception aborted the normal flow of execution. After the successful completing the try-catch block, the next statement that prints "after the try-catch block" is executed.
The finally block
It's possible to add a finally block to a try-catch template. The code inside this block will always be executed, regardless of whether there was an exception or no.
try {
// a code throwing an exception
} catch (Exception e) {
// an exception handler
} finally {
// the code always be executed
}
The finally block is executed after the catch block.
Also, it's possible to write a try with a finally without any catch blocks:
try {
// a code
} finally {
// a finally code
}
In this case, the finally block is executed right after the try block.
Example 1. Here is an example to better understand the order of execution of the try-catch-finally statement:
try {
System.out.println("inside the try block before an exception occurs"); // it will be printed
} catch (Exception e) {
System.out.println("inside the catch block"); // it won't be printed
} finally {
System.out.println("inside the finally block"); // it will be printed
}
System.out.println("after the try-catch-finally block"); // it will be printed
The output:
inside the try block before an exception occurs
inside the finally block
after the try-catch-finally block
As you can see, the finally block is executed if no exceptions.
Example 2. In another example, an exception occurs inside the try block.
try {
throw new RuntimeException("An exception occurs"); // it throws a RuntimeExcepton
} catch (Exception e) {
System.out.println("inside the catch block"); // it will be printed
} finally {
System.out.println("inside the finally block"); // it will be printed
}
System.out.println("after the try-catch-finally block"); // it will be printed
The output:
inside the catch block
inside the finally block
after the try-catch-finally block
So, the finally block is also executed if an exception happens. Moreover, it's executed even an exception happens inside a catch block.
Catching multiple exceptions
It's always possible to write a code for catching any exceptions using only single handler like this:
try {
// a code trowing exceptions
} catch (Exception e) {
System.out.println("Something bad");
}
This code can handle exceptions of any types but it gives us too little information about the problem.
If we'd like to invoke different handlers depending on the type of exceptions, we can write more than one catch blocks.
See the example with two catch blocks:
try {
// a code that throws exceptions
} catch (IndexOutOfBoundsException e) {
// handling the IndexOutOfBoundException and its subclasses
} catch (Exception e) {
// handling the Exception and its subclasses
}
When an exception occurs the first suitable catch block is determined (by the exception type). Matching goes from top to down.
The general idea: a catch block with a base class has to be written after all blocks with subclasses. Otherwise, it won't be compiled.
Since Java 7, if two exceptions can be handled in the same way you can write multi-catch syntax:
try {
// a code that may throw exceptions
} catch(SQLException | IOException e) {
// handling SQLException and IOException and their subclasses
} catch(Exception e) {
// handling any exceptions
}
In the code above SQLException and IOException (alternatives) are separated by the character |. They will be handled in the same way.
The general idea: alternatives in a multi-catch statement cannot be related by subclassing.
The try-with-resources statement
Sometimes, we work with external resources such as files or a database or a web-service. It's an error-prone interaction. What if the file does not exist or the connection to the database is already lost.
To interact with external resources we need handling exceptions. Moreover, after working we need to close them. Before the Java 7, the try-catch-finally statement was a common way for handling exceptions and closing external resources. It looked like:
InputStream is = null;
try {
is = new FileInputStream("test.txt"); // creating an instance for reading from the file
// do something with the stream
} catch (IOException e) {
// process an exception
} finally {
try {
if (is != null) {
is.close(); // closing the stream
}
} catch (IOException e) {
// ignoring
}
}
Wow! Why too difficult? Each Java developer wrote a similar template a bit in its own way.
It's because inside the try block exception can occur and inside the finally block as well. If an exception occurs inside the finally block, the resource may be not closed and there will be a leak. Moreover, an exception thrown in finally can hide an exception is thrown in the try block. Then we may not know the root cause of the problem.
Since Java 7 we have a standard way to do the similar logic: the try-with-resources statement. Here is an example:
try (InputStream is = new FileInputStream("test.txt")) {
// do something with the stream
} catch (IOException e) {
// process an exception
}
This code guarantees that the stream will be closed in any way. Read more about try-with-resources in topics related to working with external resources.
Handling checked and unchecked exceptions
The try-catch statement can handle both groups of exceptions: checked and unchecked. But there is only one difference - checked exceptions must be caught (using try-catch) or declared to be thrown (using the throws keyword) when unchecked exceptions may not be.
Writing a complex example
Let's write an example. The method parseNumbersFromString must take a string and converts it to the array of integers.
Note: we recommend you to read this example carefully and try to run it locally. The example contains some common ideas for handling exceptions.
1) Using unchecked exceptions
Here is a version of the method that may throw NullPointerException and NumberFormatException.
public static int[] parseNumbersFromString(String s) {
String[] parts = s.split("\\s+"); // splitting the string by whitespaces
int[] numbers = new int[parts.length]; // the array for storing converted numbers
for (int i = 0; i < numbers.length; i++) {
numbers[i] = Integer.parseInt(parts[i]);
}
return numbers;
}
If we call it with the argument "4 5 abc 8", it throws the NumberFormatException.
But the method doesn't declare any exceptions because it throws only unchecked exceptions. Also, we don't need to write try-catch blocks in the method or in the calling method (but this does not mean that they do not need to be written).
Here is the calling method of parseNumbersFromString:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // an object to read from the standard input
String line = scanner.nextLine(); // a line with numbers
int[] numbers = parseNumbersFromString(line); // converted array with numbers
System.out.println(Arrays.toString(numbers)); // printing the array like "[1 2 3]"
}
There are also no any try-catch blocks and the code can be compiled.
If the program reads the string "1 2 3 4 5" the output is:
[1, 2, 3, 4, 5]
If the program reads a string with an incorrect number, for instance, "19 20 qqq", then it is aborted with the exception:
Exception in thread "main" java.lang.NumberFormatException: For input string: "qqq"
Of course, we can add a try-catch block and handle this exception.
2) Using checked exceptions
But what if the method will throw a checked exception? Then it must be caught or declared to be thrown. Otherwise, the code won't be compiled.
First, let's declare our own exception named ParsingArrayException. It extends the class Exception. It's a checked exception.
public class ParsingArrayException extends Exception {
public ParsingArrayException(String msg, Exception cause) {
super(msg, cause);
}
}
The class has one constructor that takes two parameters: message and cause (another exception).
The modified method parseNumbersFromString throws the checked exception if something bad:
public static int[] extractIntNumbersFromString(String s) throws ParsingArrayException {
try {
String[] parts = s.split("\\s+");
int[] numbers = new int[parts.length];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = Integer.parseInt(parts[i]);
}
return numbers;
} catch (Exception cause) {
throw new ParsingArrayException(
String.format("The string '%s' cannot be parsed as an array of numbers", s),
cause // it's a good practice not to lose the original exception
);
}
}
We wrap the original exception because it's a good practice not to lose it.
As you can see, the ParsingArrayException is written in the method declaration because it's a checked exception. Otherwise, the code won't be compiled. The compiler says that the checked exception must be caught or declared to be thrown.
Let's handle the exception. Here is a new calling code:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
String line = scanner.nextLine();
int[] numbers = extractIntNumbersFromString(line);
System.out.println(Arrays.toString(numbers));
} catch (ParsingArrayException e) {
System.out.println(e.getMessage());
}
}
Now it's compiled and works pretty well.
If the program reads the string "11 12 22 34" the output is:
[11, 12, 22, 34]
If the program read the string "11 xx 18 19 20" the output is:
The string '11 xx 18 19 20' cannot be parsed as an array of numbers
Conclusion
So, we can successfully handle checked and unchecked exceptions, but checked exceptions must be caught or declared to be thrown when unchecked exceptions may not be.
Some advice about exception handling:
- it's often convenient to create a hierarchy of exceptions for your application;
- it's a good practice do not lose the original exception if you throw a new exception from a catch block;
- an exception should be handled in a method that has sufficient information to make the correct decision, otherwise, the exception must be thrown to the calling method;
- if you process an external resource, then use the try-with-resources statement (since Java 7);
- if you handle several exceptions using the same code, try to use multi-catch syntax with alternatives (since Java 7).