ByteIntroduction

Get started with handling exceptions using Java

Skills:

Error Handling

Objective

Understand Exception Handling

Background/Recap

Testing can catch most of the common errors within programs. Take the scenario of a software for an ATM machine. Edge cases like incorrect PIN, customer trying to retrieve more money than what’s available in his account etc. will get included in the tests. What happens with scenarios that go untested? (eg: Customer tries to dispense an amount larger than the money remaining in the ATM). In this case, the ATM machine shouldn’t just break apart and shut down due to an error. The software should either handle issues like these in a meaningful way or set up graceful termination of the program.


Assuming errors won’t occur and not caring about error scenarios can have a serious impact. Also, the cost to rectify errors once the software is deployed can be significantly higher.


Exception Handling is how computer programs improvise or recover from unexpected situations without going down or ending up in an unrecoverable state. Exception handling ensures graceful handling of errors.

Primary goals

  1. Understand the need for handling exceptions

  2. Understand exception handling in Java

  3. Understand classification of exceptions in Java

  4. Understand handling multiple exceptions at once

  5. Understand cleaning up on exceptions

Objective

Understand Exception Handling

Background/Recap

Testing can catch most of the common errors within programs. Take the scenario of a software for an ATM machine. Edge cases like incorrect PIN, customer trying to retrieve more money than what’s available in his account etc. will get included in the tests. What happens with scenarios that go untested? (eg: Customer tries to dispense an amount larger than the money remaining in the ATM). In this case, the ATM machine shouldn’t just break apart and shut down due to an error. The software should either handle issues like these in a meaningful way or set up graceful termination of the program.


Assuming errors won’t occur and not caring about error scenarios can have a serious impact. Also, the cost to rectify errors once the software is deployed can be significantly higher.


Exception Handling is how computer programs improvise or recover from unexpected situations without going down or ending up in an unrecoverable state. Exception handling ensures graceful handling of errors.

Primary goals

  1. Understand the need for handling exceptions

  2. Understand exception handling in Java

  3. Understand classification of exceptions in Java

  4. Understand handling multiple exceptions at once

  5. Understand cleaning up on exceptions

Download and run source code

  • Create ~/workspace/bytes/ directory and cd to it

mkdir -p ~/workspace/bytes/

cd ~/workspace/bytes/

  • Download the source code to the ~/workspace/bytes/ directory from here using one of the following commands:

git clone https://gitlab.crio.do/crio_bytes/me_exception_handling.git

git clone git@gitlab.crio.do:crio_bytes/me_exception_handling.git

  • To compile a java file, App.java, execute

javac App.java

  • To run this compiled java file, execute

java App

  • Verify by compiling the App.java file that you downloaded, you’ll see an error message since it is not complete.

Why is Handling an Exception important?

Exception handling, at first, might seem like an additional burden. However, the real benefit kicks in when the project becomes large and it becomes hard to "remember" all your code. This is also the case when you are working as a team and different components of the project will be written by multiple developers, who can all make changes independently.


Let’s look at an example to see the value of this.


For Better User Experience


What would you feel if you saw this screen when you eagerly typed in some query on Quora?

image alt text

For someone who has seen a lot of error traces and loves this, the next step would be to go through the adventure of figuring out what the error is saying. But, for a layman user, this won’t make any sense and the user will leave the page. Not only did the user not get what they wanted, but was also not given any information to figure out what went wrong.


How would the user emotion be if the screen was like this?

image alt text

Here, the user is shown a much more "usable" message that communicates what happened. Users would appreciate this and try out the suggestions mentioned here.


Exception Handling allows developers to recover from such errors and handle it gracefully with a clear message to users.

Note

The images are only representative and not real website images from Quora


Reproducing errors


Though digging into stack traces and finding the root cause of an error will be exciting sometimes, you wouldn’t want to deal with it on a frequent basis. Debugging code takes time and effort.


With any codebase that you work on, it is difficult to debug an error by remembering all the functionality of different classes or how they interacted with each other. Proper utilisation of exception handling procedures can save you a lot of debugging time by including more information when an error is seen.

image alt text

The above error trace tells you a MismatchedInputException occurred due to so and so reason, as well as the exact location of code where it happened. You cannot be sure, without taking a look at the code, as to what the methods were doing. Then, you need to come up with a hypothesis, do some checks and figure out the exact reason.


What if the error screen was something like this?

image alt text

The exact context in which the error occurred (when reading a file), cause of the error (cannot deserialize a String) and possible solution (validating the file format) is already there for you. Though, a level of detail like this may not be possible for all scenarios, logging the parameter values and other additional information will help developers debug your application much faster and with less effort.


By going the extra mile of adding some additional information when developing the project you are doing a favor to yourself and to anyone else maintaining the project.

Declaring Exceptions

Let’s get to know the files we have in the project that we fetched during the Setup task. The logic is implemented to authenticate users. This is done by checking the username and password they entered against a data source. Application has functionality to perform authentication both when the user is offline (using LocalStorage.java which gets username and password data from a local file) and online (using NetworkStorage.java which fetches this information from a site on the cloud)

  • App.java - contains logic to check if username and password matches

    • loginOffline(username, password) - use locally saved (file based) data to authenticate user

    • loginOnline(username, password) - use data saved on cloud to authenticate user

    • main() - serves as the frontend (for now) where users will input username and password data

  • LocalStorage.java - performs storage operations and fetches login data from UnameToPass.txt file saved locally

    • getPassword(username) - fetches password for a particular username

    • getUsernameToPasswordMapping() - fetches all username to password mappings from the UnameToPass.txt file

  • NetworkStorage.java - similar functionality as LocalStorage.java, but fetches data over the Internet. (We’ll look at this in the later milestones)


The main() method of the App class is calling its loginOffline(username, password) method with parameters Dhoni and dhoni**Password. The methods provided by the LocalStorage class is used to fetch the password corresponding to a username. The loginOffline() method checks if the passwords match and returns a message to the user accordingly


LocalStorage localStorage = new LocalStorage();

public String loginOffline(String username, String inputPassword) {

    String expectedPassword = localStorage.getPassword(username);

    if (expectedPassword.equals(inputPassword)) {

        return "Login successful";

    } else {

        return "Invalid password!";

    }

}

public static void main(String[] args) {

    App app = new App();

    System.out.println(app.loginOffline("Dhoni", "dhoniPassword"));

}

The UnameToPass.txt file contains the username-password mapping which the LocalStorage class utilizes. Check it, does username, Dhoni map to password, dhoni**Password? Try compiling and running the App.java file.


image alt text

We see an error!


Let’s understand the error printed out. It’s trying to tell us there are a couple of exceptions, FileNotFoundException and IOException, which are not caught. We must either catch them or declare the methods to throw these exceptions. The lines causing the error are in the LocalStorage.java file. This file also gets compiled when compiling App.java as it’s used within it.

image alt text

You’ll see VS code underlining the exact lines where the compilation error occurs, in red. Hover over these and you’ll find that the class/method declaration has an extra throws keyword followed by one of the exceptions we saw earlier. To resolve the compilation error, you’d remember from the error seen earlier that the options were

  1. Catch the error OR

  2. Declare exceptions to be thrown.

image alt text

As you’d have guessed, throws keyword is used to say that a class/method can throw an exception. This leaves the responsibility of dealing with the exception (FileNotFoundException ) to the method that uses the FileReader class. Here, the getUsernameToPasswordMapping() method has to deal with it.


So, the getUsernameToPasswordMapping() method has two options.

  • Throw the exception using throws so that the exception needs to be handled by any other function calls getUsernameToPasswordMapping()

  • Catch the exception and handle it.

Let’s explore both the methods one by one.


TODO: Modify getUsernameToPasswordMapping() to handle the exceptions by throwing both the exceptions. Compile the App.java file again. Does it show errors at lines in getUsernameToPasswordMapping() like before or in a different method?


public Map<String, String> getUsernameToPasswordMapping() throws FileNotFoundException, IOException {


Similarly, you’ll see the same issue for the getPassword() method as it’s using the getUsernameToPasswordMapping() method.

image alt text


TODO: Modify getPassword() to throw the exceptions it needs to handle like we did with getUsernameToPasswordMapping().

Catching Exceptions

With the exceptions declared to be thrown in both the methods, you’ll see that the red lines vanished. What will happen on compiling the App.java class now? Think, then act.

image alt text

Compiler is now complaining about not catching or declaring the exceptions in the App.java file. The error is due to using the getPassword() method of the LocalStorage class which we declared to throw exceptions in the previous section. With what we have learnt till now, regarding exception handling, we can use the throws to handle exceptions. Ensure App.java compiles by throwing the exception like we did for the other two methods before. Run it.


crio-user@crio-demo:$ javac App.java 

crio-user@crio-demo:$ java App

Login successful

crio-user@crio-demo:$ 

The loginOffline() method is user-facing (which we’re simulating using the main() method for now), meaning that the user gets a message "Login Successful" in this case. That’s good, we have handled the Exception successfully. Or, have we?


What would the user see if loginOffline() causes an exception? (Remember we declared it to throw exceptions).

  • Change the name of the file that the method getUsernameToPasswordMapping() reads from, in the LocalStorage class

image alt text

  • Run the App.java file again (don’t forget to compile first).

image alt text

This is exactly what we had discussed in Milestone 1. It is not helpful for the users to see this.


If you recall, the compiler had given you two options to deal with the exceptions earlier. We’ve seen the "throws" option. Now, let's explore the second option which is "catch". Catching an exception is where we define beforehand how we want to recover when an exception occurs. Java provides the try-catch statement for that. The part of the code that can cause an exception is wrapped within the try clause and how we want to recover from an exception is wrapped within the catch clause.

(Remove any throws keyword usage in App.java, so we can explore this catch option)


TODO: Use the try-catch statement to catch IOException inside the loginOffline() method (IOException is the parent class of FileNotFoundException. So, catching FileNotFoundException won’t be required after catching IOException)

image alt text

What does the user see when there’s an error now? Compile and execute to find out. Isn’t it a much better message for the user?


For the developers to have debugging information whenever the exception occurs, we can use the printStackTrace() method of the exception object and configure it to be logged to a log file. This will provide information about where exactly the exception occurred.


TODO: Use the debugger to check the flow of the program

  1. In the normal case

  2. When there’s an exception


Set breakpoints on these lines for that

  • expectedPassword = localStorage.getPassword(username); in App.java

  • unameToPassMap = getUsernameToPasswordMapping(); in LocalStorage.java

  • FileReader fileReader = new FileReader(loginDataFile); in LocalStorage.java

(Hint: Set a breakpoint inside the catch block and click Debug on top of main() in VS code)

Note

Don’t forget to reset the file name to the correct one, in the LocalStorage class

Curious Cats

  • Earlier in the milestone, both FileNotFoundException and IOException were thrown in the LocalStorage class methods. Try removing FileNotFoundException and compile again. Does the compiler complain now?

Errors and Exceptions

We looked at how to handle exceptions in the previous milestone.


However, there are scenarios like the server running out of memory or the case of stack overflow which the application shouldn’t try to catch, since these are concerned with the system itself rather than being an issue with the application. Unfavourable situations like the are Errors in Java and are used by the Java Virtual Machine (JVM). Examples are OutOfMemoryError and StackOverflowError.


Throwable is the parent class for both the Error and the Exception classes. Both Error and Exception classes have their own hierarchy of errors and exceptions.OutOfMemoryError and StackOverflowError are sub-classes of the Error class


image alt text

Exception Hierarchy in Java (Source: https://www.javamadesoeasy.com/)

Exceptions like we saw earlier are also unexpected events like a file not being found, error during file I/O operation. But here, it’s possible for the application to recover, this is the main difference between Errors and Exceptions. IOException subclasses the Exception class.IOException class is again the parent class for other exceptions like the FileNotFoundException. This was why the compiler didn’t throw an "unreported exception" error even if we only caught the IOException class inside the loginOffline() method.

Checked vs Unchecked Exceptions

Exceptions in Java fall into two categories - Checked and Unchecked. Checked exceptions must either be declared to be thrown or caught. These are "checked" by the compiler and it throws an error if we fail to do that. This is what happened when we ran the App class earlier. We didn’t get to run the class but failed at the compilation stage itself.


TODO: Try creating a new method in the App class which uses the localstorage.getPassword() method and see if it compiles.


Note: Comment out the new method you just created or ensure you catch the Exceptions inside it


Unchecked exceptions aren’t checked by the compiler. For instance, NullPointerException is a common exception caused when we try to call some methods on an object with null value. These usually signify programming errors and hence is a good practice not to catch them.


TODO: Change the username in main() method from Dhoni to your name. Compile and run the App class now. What happened?


In LocalStorage::getPassword(), the get() method provided by the Map class returns null if the key isn’t present. Here, using the equals method on the null object inside loginOffline causes NullPointerException.

image alt text

TODO: Use the debugger to check the program flow in this scenario. Does control reach the catch block? Add an additional check to see if the returned value is null and return an appropriate message to the user to handle this cleanly.


All subclasses of the RuntimeException class are unchecked, so are that of the Error class.

Using catch statement for multiple exceptions

The loginOffline() method throws FileNotFoundException when it can’t find the file locally to read login information from. A new requirement has come in to improve the handling in this case. The NetworkStorage class methods are to be used to fetch the login information from the cloud server. A loginOnline() method has been provided for this purpose.


Java allows multiple catch blocks along with a try block where we can perform separate actions for different exceptions.


TODO:

  • Use multiple catch statements inside the loginOffline method.

    • Uncomment the loginOnline() in App.java

    • First catch the FileNotFoundException and recover from this exception by calling the loginOnline() method.

    • Use a second catch statement for the IOException and handle as before.

  • Change the input file name in the getUsernameToPasswordMapping() method to cause a FileNotFoundException and confirm that the application runs successfully for this error case.


try {

    expectedPassword = localStorage.getPassword(username);


} catch (FileNotFoundException exceptionObject) {

    // log error trace to file

    // exceptionObject.printStackTrace();

    expectedPassword = loginOnline(username, inputPassword);


} catch (IOException exceptionObject) {

    // log error trace to file

    // exceptionObject.printStackTrace();

    return "Internal error, please try again later";

}

  • Use the debugger to see the program flow. Answer the following questions

    • When the FileNotFoundException occurs, are all of the catch blocks entered? (alter the input file name in getUsernameToPasswordMapping() to cause the exception)

    • The throw keyword can be used to explicitly throw an Exception. Use it to throw an IOException inside the try block in loginOffline() method. As IOException is the super-class of the FileNotFoundException class, does the control go inside the first catch block?

Curious Cats

  • What happens if the IOException is caught first and then the FileNotFoundException?

Cleanup during Exceptions

Inspect the getUsernameToPasswordMapping() method in NetworkStorage class. A BufferedReader object is created and also closed there (see close() method). It is good practice to always release any system resources like allocated memory, open file handlers etc. after their usage. Releasing resources is an example of cleanup.


BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.openStream()));

Uncomment the commented out section in the main() method and run it (Hit Ctrl + c when you start seeing errors). By uncommenting these lines, main() now calls the loginOnline method concurrently, a large number of times. (Tip: To uncomment a block of code, highlight the lines and hit Ctrl + /)


The error was caused due to an IOException and the error message tells that the server returned a HTTP 429 response code. What does this response code mean? Why do you think it occurred?

image alt text

The point of failure in the code is inside the getUsernameToPasswordMapping() method and on checking the exact line causing the error, you’ll find that it’s the line where we create a new BufferedReader instance. Find out what caused this error by hovering over each keyword in that line and see if any of them throws an IOException


Checking the program’s flow when an exception occurs, we see that the IOException would return the control from the getUsernameToPasswordMapping() and won’t run the code that comes after the code that caused the Exception. This means the BufferedReader instance we created won’t be closed.


Java provides the finally block to wrap code which should get executed irrespective of an exception occuring or not. The finally block can be used as part of a

  • try-finally block → wrap code that can throw exception inside try and include code that must be executed always in finally

  • try-catch-finally block → the try-catch block (e.g. like we used inside loginOffline()) gets an additional finally section which will always get executed


TODO:

  • Use finally block inside getUsernameToPasswordMapping() to ensure the BufferedReader resource is closed even if an exception occurs (do this for both the LocalStorage and NetworkStorage classes)

  • Use debugger to understand program flow

    • Is the finally block visited when there’s no exception thrown?

    • Is the finally block visited only for IOException? (Change the URL to something random)

Curious Cats

  • Forgetting to use the finally block for ensuring a resource is closed can be easy. Is there some way to ensure any open resource gets automatically closed after its usage is over?

Summary

  • We saw why handling exceptions are critical from both a user’s perspective and a developer’s perspective.

  • Exceptions can be either caught or declared to be thrown in Java

    • The try-catch block is used for catching exceptions

    • The throws keyword is used along with a class/method declaration to denote that it throws an Exception

  • Java has two higher level classes, Exception and Error to denote unexpected scenarios that occur during the run of the program. Both of these inherit the Throwable class

    • Exceptions are usually handled by the programmer

    • Errors are caused due to system errors and hence not handled

  • Exceptions that the compiler checks for are called Checked Exception. These are the ones that must be caught or declared to be thrown or otherwise the code won’t compiler

  • Exceptions that subclasses the RuntimeException class isn’t checked by the compiler and hence called Unchecked Exception

  • Find the

    • Solution code here

    • Pointers to the Curious Cats questions here

  • Best Practices

    • Handle all checked exceptions

    • Close or release resources in the finally block

    • Always provide meaningful message on Exception

    • Avoid empty catch blocks

  • Further Reading

Newfound Superpowers

  • Knowledge of Exception Handling in Java

Now you can

  • Explain why exception handling is important

  • Provide better user experience to the product users

  • Provide better debugging experience for developers

  • Handle exceptions in different scenarios for Java code