5 Rules about Catching Exceptions in Java
- Details
- Written by Nam Ha Minh
- Last Updated on 09 July 2019   |   Print Email
This article dives deeply into catching exceptions in order to help you understand more about exception handling in Java.
1. Catching multiple exceptions
There are as many catch blocks as the number of exceptions which can be thrown from the code safeguarded by the try block. Here’s an example:
try { LineNumberReader lineReader = new LineNumberReader(new FileReader("hello.txt")); String line = lineReader.readLine(); lineReader.close(); System.out.println(line); } catch (FileNotFoundException ex) { System.err.println("Find not found"); } catch (IOException ex) { System.err.println("Error reading file"); }
In the above code, the first line in the tryblock can throw FileNotFoundException if the specified file could not be located on disk; and the next two lines can throw IOException if an error occurred during the reading and closing the file. Hence there are two catch blocks for handling both exceptions.
2. The order of catch blocks does matter
If the protected code can throw different exceptions which are not in the same inheritance tree, i.e. they don’t have parent-child relationship, the catch blocks can be sorted any order.
However, keep in mind this rule: if the exceptions have parent-child relationship, the catch blocks must be sorted by the most specific exceptions first, then by the most general ones.
In the above example, FileNotFoundException is a child of IOException so its catch block must come first. If we try to catch the IOException before FileNotFoundException, the compiler will issue an error like this:
error: exception FileNotFoundException has already been caught
Why? It’s because if we handle the most general exceptions first, the more specific exceptions will be omitted, which is not good, as Java encourages handling exceptions as much specific as possible.
3. Catching one exception for all
If we catch the most general exception first, then we also catch other exceptions which are subtypes of the general exception. For example, the above example can be re-written to catch only the IOException which is also parent of FileNotFoundException:
try { LineNumberReader lineReader = new LineNumberReader(new FileReader("hello.txt")); String line = lineReader.readLine(); lineReader.close(); System.out.println(line); } catch (IOException ex) { System.err.println("Error reading file"); }
Let’s see another example. Consider the following code:
public static void doCrypto(int cipherMode, String key, File inputFile, File outputFile) throws CryptoException { try { Key secretKey = new SecretKeySpec(key.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(cipherMode, secretKey); FileInputStream inputStream = new FileInputStream(inputFile); byte[] inputBytes = new byte[(int) inputFile.length()]; inputStream.read(inputBytes); byte[] outputBytes = cipher.doFinal(inputBytes); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(outputBytes); inputStream.close(); outputStream.close(); } catch (NoSuchPaddingException ex) { System.err.println("no padding"); } catch (NoSuchAlgorithmException ex) { System.err.println("no algorithm"); } catch (InvalidKeyException ex) { System.err.println("invalid key"); } catch (BadPaddingException ex) { System.err.println("bad padding"); } catch (IllegalBlockSizeException ex) { System.err.println("illegal block"); } catch (IOException ex) { System.err.println("error reading file"); } }
Here, there are 6 catch blocks to handle each exception individually. We can catch all these exception by only one catch block as following:
public static void doCrypto(int cipherMode, String key, File inputFile, File outputFile) throws CryptoException { try { Key secretKey = new SecretKeySpec(key.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(cipherMode, secretKey); FileInputStream inputStream = new FileInputStream(inputFile); byte[] inputBytes = new byte[(int) inputFile.length()]; inputStream.read(inputBytes); byte[] outputBytes = cipher.doFinal(inputBytes); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(outputBytes); inputStream.close(); outputStream.close(); } catch (Exception ex) { System.err.println("error"); } }
It’s easy to understand because Exception is the supertype of all exceptions. However, this practice is not recommended, as it makes the programmers lazy: catching one is obviously quicker than catching many. That means the programmers do not take responsibility to handle exceptions carefully. The good practice recommends catching specific exceptions so the program can handle different situations well. Java doesn’t prohibit you from catching one for all, but when doing so, you should have good reasons to do that.
4. Grouping multiple exceptions in one catch
Since Java 7, we can combine multiple exceptions in a single catch clause. This becomes very handy in case we want to apply the same handling for those exceptions. For example, the above code can be re-written using a multi-catch statement like this:
public static void doCrypto(int cipherMode, String key, File inputFile, File outputFile) throws CryptoException { try { Key secretKey = new SecretKeySpec(key.getBytes(), “AES”); Cipher cipher = Cipher.getInstance(“AES”); cipher.init(cipherMode, secretKey); FileInputStream inputStream = new FileInputStream(inputFile); byte[] inputBytes = new byte[(int) inputFile.length()]; inputStream.read(inputBytes); byte[] outputBytes = cipher.doFinal(inputBytes); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(outputBytes); inputStream.close(); outputStream.close(); } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException | IOException ex) { System.err.println("error"); } }
Note that we can group only un-related exceptions together. That means it’s illegal to group exceptions which have parent-child relationship. For example, it’s illegal to write a multi-catch statement like this:
try { LineNumberReader lineReader = new LineNumberReader(new FileReader("hello.txt")); String line = lineReader.readLine(); lineReader.close(); System.out.println(line); } catch (FileNotFoundException | IOException ex) { System.err.println("Find not found"); }
The compiler will complain:
error: Alternatives in a multi-catch statement cannot be related by subclassing
5. What should you do in the catch blocks?
It’s up to you to write anything inside the catch blocks. Remember the main purpose of the catch blocks is to recover the program from the exceptions and continue execution, such as notifying the user about the error, ask he or she to wait, try again or exit, etc.
Typically, you can do the following things in the catch blocks (not limited to):
- Print out the exception details via System.err class’ methods, then exit the method:
System.err.println(ex); System.err.println(ex.getMessage());
- Print the full stack trace of the exception and exit the method:
ex.printStackTrace();
- Log the exceptions then exit the method. You can use Java’s built-in logging functionalities or a third-part library like log4j.
- Display an error message dialog in case the program is a desktop application (Swing or JavaFX).
- Redirect the user to an error page in case the program is a web application (Servlets & JSP).
Other Java Exception Handling Tutorials:
- Getting Started with Exception Handling in Java
- How to create custom exceptions in Java
- How to throw exceptions in Java - the differences between throw and throws
- Java Checked and Unchecked Exceptions
- Java exception API hierarchy - Error, Exception and RuntimeException
- Understanding Exception Stack Trace in Java with Code Examples
- Understanding Java Exception Chaining with Code Examples
- What you may not know about the try-catch-finally construct in Java
Comments