Error Checking and Exception Handling
AAHC

Click on A to make all fonts on the page smaller.

Click on A to make all fonts on the page larger.

Click on HC to toggle high contrast mode. When you move your mouse over some bold words in high contrast mode, related words are automatically highlighted. Text is shown in black and white.


Being Prepared for Users

Creating applications involves three basic tasks: writing the code that performs the desired function (creating the model), providing a clear and easy to use interface (or view), and making sure that users don't break the application.

Crashes

In the java4_Lesson1 project, go to the salesGUI package, open the Main.java class, and Run it. Now give it these values:

Click All Set. You'll see a lot of red in the Console:

This isn't a problem for programmers--we can see the console, so we can see the Exception too. But it's a problem for users, because the application view doesn't change at all, so they aren't even aware that they've made an error. They can carry on and try the menu items, but they won't get their results.

In the application that's currently running, select Options | Results. You'll notice even more red in the Console:

Again, the user still doesn't see any of this; they just know that the lousy program isn't working!

Close the running application using the menu item File | Exit.

If an application is open and running, it will continue to use the same .class (old compiled code) that it was opened with, even if you make changes to the application and save it again. If you edit, resave, and rerun an application, but still have the older (erroneous) code running, it can cause frustration. Always make sure to close an application properly, before editing and running a new version.

Expect the Unexpected

Exceptions occur even to our most well-thought-out Java code plans. In fact, they are so common that Java has a class named Exception.

Go to the java.lang package. Scroll down to the Exception Summary, then to the Exception class. There are quite a few Direct Known Subclasses (we edited most of them out in the image below though, because the list was so long):

And that's just the beginning. Go back to java.lang's Exception Summary. Scroll down to RuntimeException (just one of the Direct Known Subclasses of Exception). Click on RuntimeException and check out all of its Direct Known Subclasses.

Programs and users may behave in an infinite number of unexpected ways. When the unexpected happens, our code (with the help of Java) will throw an Exception (the java.lang.Exception class extends (or inherits from) the class java.lang.Throwable).

So, what's an exception? Oracle's Java tutorial says, "An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions." If we don't want our programs to crash and cause our users to become frustrated, then we need to plan for all potential Exceptions.


Handling Exceptions
Finding the Problem

When Java throws an Exception, it tells us which type of Exception it was and where it occurred.

In our first exception, Java provided a long debugging trace of its location. Usually, the last couple of lines in a trace are the most important for programmers. For example, when the user entered the value of 3.4 in the InputPanel, we saw:

We can tell from the Exception that an input string of 3.4 caused a java.lang.NumberFormatException. We can also determine that the exception occurred in the InputPanel.java class at line number 76.

Open the InputPanel.java class and display the line numbers (on the left side bar, right-click and choose Show Line Numbers). Go to line 76. You should see sales[x] = Integer.parseInt(jtfSales[x].getText());.

Do you recognize the problem? The sales[ ] array is declared as Integer. We told Java to expect an int, but the user gave us a decimal. A decimal is not an int; it's a double or a float. So, how do we remedy this?

Fixing the Problem

Java provides a specific structure to handle Exceptions. We put potential problems into try/catch clauses. If certain code could throw exceptions, we place it in a try clause, and then provide a catch clause to make the appropriate corrections.

Try/Catch Clauses
Anticipating Exceptions

If we want to catch exceptions, we need to know when they might occur. Sometimes code provided in the API indicates that it will throw various types of exceptions. We'll address those exceptions in greater detail later, but for now, let's get a handle on the general idea. If we had researched the methods we were using carefully in the API beforehand, we could have anticipated potential problems before writing the code.

In the API, go to java.lang.Integer. Go to the parseInt(String s) method.

We could have anticipated that a user might enter a decimal number rather than an integer. Programmers need to be ready for all kinds of potentially unexpected situations.

So, how can we be prepared? Well, if the user behaves as we would like them to, the code works great. But if the user doesn't, we need to catch the Exception. If a method throws an Exception, then we should instruct our code to try that method's piece of code.

If our programs do not catch exceptions, then Java is forced to throw them farther. Java will keep throwing exceptions until something catches it, or until it gets to the "top of the stack" (more on this in a later lesson). At that point, if an exception has not been caught, it will cause errors in the console.

Making It Right: Dialog Boxes

In our example, the problem is in the type of input given by the user, so let's tell the user when something they've entered needs to be changed. We'll do that using a dialog box.

In the InputPanel class, where the exception occurred (around line 76), edit the actionPerformed() method as shown in blue:

CODE TO EDIT: InputPanel
package salesGUI;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class InputPanel extends JPanel implements ActionListener {
    JPanel topPanel, middlePanel, bottomPanel, leftPanel, rightPanel;
    JLabel[] jlSales;                        
    JButton done; 
    SalesApp app;
    JLabel prompt, doneLabel, jlSalesBar; 
    JTextField[] jtfSales; 
    JTextField jtfSalesBar; 
    int numPeople; 
    int [] sales;
    int goal;

    public InputPanel(SalesApp container, int numPeople, int gridX) {
        this.app = container;
        this.numPeople = numPeople;
        sales = new int[numPeople];

        this.setLayout(new BorderLayout());
        topPanel = new JPanel();
        topPanel.setLayout(new FlowLayout());
        middlePanel = new JPanel(new GridLayout(numPeople, gridX));
        bottomPanel = new JPanel();
        bottomPanel.setLayout(new FlowLayout());
        leftPanel = new JPanel();
        rightPanel = new JPanel();
        add("North", topPanel);
        add("Center", middlePanel);
        add("South", bottomPanel);
        add("East", rightPanel);
        add("West", leftPanel);
  
        jlSales = new JLabel[numPeople];
        jtfSales = new JTextField[numPeople];
        prompt = new JLabel("Give values for each salesperson:");
        topPanel.add(prompt);
        
        for (int x = 0; x < numPeople; x++) {
            jlSales[x] = new JLabel("Sales Person " + (x+1));
            jtfSales[x] = new JTextField("0", 8);
            middlePanel.add(jlSales[x]);
            middlePanel.add(jtfSales[x]);
        }
        jlSalesBar = new JLabel("Enter a value for the sales goal");
        bottomPanel.add(jlSalesBar);
        jtfSalesBar = new JTextField("0",8);
        //jtfSalesBar.addActionListener(new GoalButtonListener());
        bottomPanel.add(jtfSalesBar);
        doneLabel = new JLabel("Click when all are entered:");
        bottomPanel.add(doneLabel);
        done = new JButton("All Set");
        bottomPanel.add(done);
        done.addActionListener(this);
    }
    
    public void actionPerformed(ActionEvent event){
        if (event.getSource() instanceof JButton)
        {
            if ((JButton)event.getSource() == done) 
            {
                for (int x = 0; x < numPeople; x++)
                {
                    try 
                    {
                        sales[x] = Integer.parseInt(jtfSales[x].getText());  // throws NumberFormatException
                    } 
                    catch(NumberFormatException e)
                    {   
                        String messageLine1 = "Input must be whole numbers.\n ";
                        String messageLine2 = "Your decimal value " + jtfSales[x].getText() + " for Sales Person " + (x+1) +" will be truncated.\n ";
                        String messageLine3 = "You may enter a different integer and click AllSet if truncation is unacceptable.";

                        JOptionPane.showMessageDialog(this, messageLine1+messageLine2+messageLine3,"Input Error", JOptionPane.ERROR_MESSAGE);
 
                        sales[x]= (int)Double.parseDouble(jtfSales[x].getText());
                        jtfSales[x].setText(Integer.toString(sales[x]));      
                    }
                }

                app.setSales(sales);
                goal = Integer.parseInt(jtfSalesBar.getText());  // so don't have to be sure they hit enter
                app.setSalesBar(goal);
            }
        }
    }
}

Save the InputPanel class.

Run the Main class. Enter a decimal number for one of the values and click All Set.

That information helps the user and the programmer. We can provide additional Strings of information for the user in the dialog boxes too. Or we can just fix things without notifying them at all. Choosing how to respond depends on the application and the significance of each piece of data.

There are several other types of dialog box options; let's take a look at another one. Replace the catch clause inside the for loop as shown in blue:

CODE TO EDIT: InputPanel
package salesGUI;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class InputPanel extends JPanel implements ActionListener {
    JPanel topPanel, middlePanel, bottomPanel, leftPanel, rightPanel;
    JLabel[] jlSales;                        
    JButton done; 
    SalesApp app;
    JLabel prompt, doneLabel, jlSalesBar; 
    JTextField[] jtfSales; 
    JTextField jtfSalesBar; 
    int numPeople; 
    int [] sales;
    int goal;

    public InputPanel(SalesApp container, int numPeople, int gridX) {
        this.app = container;
        this.numPeople = numPeople;
        sales = new int[numPeople];

        this.setLayout(new BorderLayout());
        topPanel = new JPanel();
        topPanel.setLayout(new FlowLayout());
        middlePanel = new JPanel(new GridLayout(numPeople, gridX));
        bottomPanel = new JPanel();
        bottomPanel.setLayout(new FlowLayout());
        leftPanel = new JPanel();
        rightPanel = new JPanel();
        add("North", topPanel);
        add("Center", middlePanel);
        add("South", bottomPanel);
        add("East", rightPanel);
        add("West", leftPanel);
        
        jlSales = new JLabel[numPeople];
        jtfSales = new JTextField[numPeople];
        prompt = new JLabel("Give values for each salesperson:");
        topPanel.add(prompt);
        
        for (int x = 0; x < numPeople; x++) {
            jlSales[x] = new JLabel("Sales Person " + (x+1));
            jtfSales[x] = new JTextField("0", 8);
            middlePanel.add(jlSales[x]);
            middlePanel.add(jtfSales[x]);
        }
        jlSalesBar = new JLabel("Enter a value for the sales goal");
        bottomPanel.add(jlSalesBar);
        jtfSalesBar = new JTextField("0",8);
        //jtfSalesBar.addActionListener(new GoalButtonListener());
        bottomPanel.add(jtfSalesBar);
        doneLabel = new JLabel("Click when all are entered:");
        bottomPanel.add(doneLabel);
        done = new JButton("All Set");
        bottomPanel.add(done);
        done.addActionListener(this);
    }
    
    public void actionPerformed(ActionEvent event) {
        if (event.getSource() instanceof JButton)
        {
            if ((JButton)event.getSource() == done) 
            {
                for (int x = 0; x < numPeople; x++)
                {
                    try 
                    {
                        sales[x] = Integer.parseInt(jtfSales[x].getText());
                    } 
                    catch(NumberFormatException e)
                    { 
                        String temp = JOptionPane.showInputDialog("Decimal values are not allowed.\n Please give a whole number for Sales Person " + (x+1) + ": ");
                        sales[x] = Integer.parseInt(temp);
                        jtfSales[x].setText(Integer.toString(sales[x]));      
                    }
                }
 
                app.setSales(sales);
                goal = Integer.parseInt(jtfSalesBar.getText());  
                app.setSalesBar(goal);
            }
        }
    }
}

Save the InputPanel class.

Run the Main class. Enter a decimal number for one of the values and click All Set:

For more on dialog boxes, see the Oracle tutorial on How to Make Dialogs.

Types of Exceptions

The Java programming language uses exceptions to handle errors and other exceptional events. Exceptions are unusual conditions that a well-written application will anticipate and remedy. Java provides two main types of exceptions: checked and unchecked. Checked exceptions can be checked at compile time. All exceptions are checked exceptions, except for those that are instances of the Error and RuntimeException classes and their subclasses.

Checked Exceptions

If a method has a checked exception, Java informs the programmer using the method. The class that uses the method will not compile (or Eclipse will report errors) and the programmer will not be able to run the program until the exception in the code has been handled; the programmer is forced to handle that exception.

In addition, programmers can often anticipate problems that could occur in a method they have written. The author of the method is obliged to warn other programmers who may use it, that such problems are a possibility. A good programmer will handle those problems within the application. Programmers must consider other programmers, as well as users when writing methods and applications:

  • A method's author needs to make sure that other programmers who use the method don't experience surprise failures.
  • An application's author needs to make sure that the users of their application don't have surprise failures.

Of course, the author of a method can't always anticipate which environment a programmer will use, or the type of application a programmer may want to create. Because of such variables, the method author can't predict how each application might handle a problem. The best the method author can do is to inform users of the method that a problem might exist, and that using the method might throw an Exception. Then the method's author should include throws in the method definition.

Unchecked Exceptions

Errors, Runtime Exceptions, and their subclasses are unchecked exceptions. The code we wrote to retrieve user-entered sales values had the potential to present the problems associated with unchecked exceptions. Its specification in the API clearly stated that it throws a NumberFormatException. But we were still able to compile and run the code initially without a try/catch clause. Why?

Go to java.lang.NumberFormatException in the API and look at its class hierarchy:

OR

Open InputPanel.java in the Editor. Go to the line that specifies the NumberFormatException in the catch clause. Highlight it. Right-click and choose Open Type Hierarchy:

A hierarchy window opens in the left panel (there are actually two panels there):

NumberFormatException is a subclass of RuntimeException. All exceptions are checked exceptions, except for those that are instances of the Error and RuntimeException classes, and their subclasses.

Sometimes exceptions arise when a program is run, depending on which variables are present at that particular time. These are called Runtime Exceptions.

The exceptions that we have looked at in this lesson have been unchecked (Runtime Exceptions), because the compiler cannot anticipate what a user will enter. So, even though the method java.lang.Integer.parseInt(String s) states that it might throw an exception, Java allowed the code to compile. As the Oracle Tutorial states:

Runtime exceptions represent problems that are the result of a programming problem, and as such, the API client code cannot reasonably be expected to recover from them or to handle them in any way. Such problems include arithmetic exceptions, such as dividing by zero; pointer exceptions, such as trying to access an object through a null reference; and indexing exceptions, such as attempting to access an array element through an index that is too large or too small.

Because such exceptions can happen anywhere in a program, and often runtime exceptions are not easy to spot, the compiler doesn't require programmers to catch runtime exceptions. But sooner or later, exceptions will make their presence known. The Java Virtual Machine is merciless and won't hesitate to broadcast the red details of our uncaught exceptions all over the console.


The Other Problem

When we ran our application at the beginning of this lesson, we saw two exceptions:

When we fixed the first exception (the NumberFormatException) with the try/catch clause, the second exception disappeared.

So, we're going to do what most sensible beginning programmers do: forget about it. But remember to expect the unexpected. That problem will pop up again.

Be prepared to see more exceptions in the coming lessons as we continue to investigate...