Exceptions

In this chapter of the Java tutorial, we work with exceptions. Java uses exceptions to handle errors.

During the execution of our application many things might go wrong. A disk might get full and we cannot save our data. An Internet connection might go down while our application tries to connect to a site. A user fills invalid data to a form. These errors can crash the application, make it unresponsive, and in some cases even compromise the security of a system. It is a responsibility of a programmer to handle errors that can be anticipated.

In Java we recognize three kinds of exceptions: checked exceptions, unchecked exceptions, and errors.

Checked exceptions are error conditions that can be anticipated and recovered from (invalid user input, database problems, network outages, absent files). All subclasses of Exception except for RuntimeException and its subclasses are checked exceptions. IOException, SQLException, or PrinterException are examples of checked exceptions. Checked exceptions are forced by Java compiler to be either caught or declared in the method signature (using the throws keyword).

Unchecked exceptions are error conditions that cannot be anticipated and recovered from. They are usually programming errors and cannot be handled at runtime. Unchecked exceptions are subclasses of java.lang.RuntimeException. ArithmeticException, NullPointerException, or BufferOverflowException belong to this group of exceptions. Unchecked exceptions are not enforced by the Java compiler.

Errors are serious problems that programmers cannot solve. For example hardware or system malfunctions cannot be handled by applications. Errors are instances of the java.lang.Error class. Examples of errors include InternalError, OutOfMemoryError, StackOverflowError or AssertionError.

Errors and runtime exceptions are often referred to as unchecked exceptions.

The try, catch and finally keywords are used to handle exceptions. The throws keyword is used in method declarations to specify which exceptions are not handled within the method but rather passed to the next higher level of the program. The throw keyword causes the declared exception instance to be thrown. After the exception is thrown, the runtime system attempts to find an appropriate exception handler. The call stack is a hierarchy of methods that are searched for the handler.

UncheckedException.java
package com.zetcode;

public class UncheckedException {

    public static void main(String[] args) {

        int[] n = { 5, 2, 4, 5, 6, 7, 2 };
        
        System.out.format("The last element in the array is %d%n", n[n.length]);
    }
}

There is a bug in the above program. We try to access an element that does not exist. This is a programming error. There is no reason to handle this error: the code must be fixed.

System.out.format("The last element in the array is %d%n", n[n.length]);

The array indexes start from zero. Therefore, the last index is n.length - 1.

$ java com.zetcode.UncheckedException 
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 7
        at com.zetcode.UncheckedException.main(UncheckedException.java:9)

A java.lang.ArrayIndexOutOfBoundsException is thrown by the runtime system. This is an example of an unchecked exception.

The Scanner class throws an InputMismatchException to indicate that the token retrieved does not match the pattern for the expected type. This exception is an example of an unchecked exception. We are not forced to handle this exception by the compiler.

UncheckedException2.java
package com.zetcode;

import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class UncheckedException2 {

    public static void main(String[] args) {

        System.out.println("Enter an integer: ");        
        
        try {
            
            Scanner sc = new Scanner(System.in);
            int x = sc.nextInt();          
            
            System.out.println(x);
            
        } catch (InputMismatchException e) {
            
            Logger.getLogger(UncheckedException2.class.getName()).log(Level.SEVERE, 
                    null, e);            
        }                  
    }
}

The error prone code is placed in the try block. If an exception is thrown, the code jumps to the catch block. The exception class that is thrown must match the exception following the catch keyword.

try {
            
    Scanner sc = new Scanner(System.in);
    int x = sc.nextInt();          
    
    System.out.println(x);           
}

The try keyword defines a block of statements which can throw an exception.

} catch (InputMismatchException e) {
    
    Logger.getLogger(UncheckedException2.class.getName()).log(Level.SEVERE, 
            null, e);            
}   

The exception is handled in the catch block. We use the Logger class to log the error.

The following code example connects to a MySQL database and finds out the version of the database system. Connecting to databases is error prone.

Version.java
package zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Version {

    public static void main(String[] args) {

        Connection con = null;
        Statement st = null;
        ResultSet rs = null;

        String url = "jdbc:mysql://localhost:3306/testdb";
        String user = "testuser";
        String password = "test623";

        try {

            con = DriverManager.getConnection(url, user, password);
            st = con.createStatement();
            rs = st.executeQuery("SELECT VERSION()");

            if (rs.next()) {

                System.out.println(rs.getString(1));
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(Version.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);

        } finally {

            try {

                if (rs != null) {
                    rs.close();
                }

                if (st != null) {
                    st.close();
                }

                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {

                Logger lgr = Logger.getLogger(Version.class.getName());
                lgr.log(Level.WARNING, ex.getMessage(), ex);
            }
        }
    }
}

An SQLException is an example of a checked exceptions. We are forced to handle it.

try {

    con = DriverManager.getConnection(url, user, password);
    st = con.createStatement();
    rs = st.executeQuery("SELECT VERSION()");

    if (rs.next()) {

        System.out.println(rs.getString(1));
    }

}

The code that might lead to an error is placed in the try block.

} catch (SQLException ex) {

    Logger lgr = Logger.getLogger(Version.class.getName());
    lgr.log(Level.SEVERE, ex.getMessage(), ex);
}

When an exception occurs, we jump to the catch block. We handle the exception by logging what happened.

} finally {

    try {

        if (rs != null) {
            rs.close();
        }

        if (st != null) {
            st.close();
        }

        if (con != null) {
            con.close();
        }

    } catch (SQLException ex) {

        Logger lgr = Logger.getLogger(Version.class.getName());
        lgr.log(Level.WARNING, ex.getMessage(), ex);
    }
}

The finally block is executed whether we received and exception or not. We are trying to close the resources. Even in this process, there might be an exception. Therefore, we have another catch block.

Throwing exceptions

The Throwable class is the superclass of all errors and exceptions in the Java language. Only objects that are instances of this class (or one of its subclasses) are thrown by the Java Virtual Machine or can be thrown by the Java throw statement. Similarly, only this class or one of its subclasses can be the argument type in a catch clause.

Programmers can throw exceptions using the throw keyword. Exceptions are often handled in a different place from where they are thrown. Methods can throw off their responsibility to handle exception by using the throws keyword at the end of the method definition. The keyword is followed by comma-separated list of all exceptions thrown by that method. Thrown exceptions travel through a call stack and look for the closest match.

ThrowingExceptions.java
package com.zetcode;

import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ThrowingExceptions {

    public static void main(String[] args) {
        
        System.out.println("Enter your age: ");

        try {

            Scanner sc = new Scanner(System.in);
            short age = sc.nextShort();
            
            if (age <= 0 || age > 130) {
                
                throw new IllegalArgumentException("Incorrect age");
            }

            System.out.format("Your age is: %d %n", age);

        } catch (IllegalArgumentException | InputMismatchException e) {

            Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE,
                    null, e);
        }
    }
}

In the example, we ask the user to enter his age. We read the value and throw an exception if the value is outside the range of the expected human age.

if (age <= 0 || age > 130) {
    
    throw new IllegalArgumentException("Incorrect age");
}

An age cannot be a negative value and there is no record of a person older than 130 years. If the value is outside of this range we throw a built-in IllegalArgumentException. This exception is thrown to indicate that a method has been passed an illegal or inappropriate argument.

} catch (IllegalArgumentException | InputMismatchException e) {

    Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE,
            null, e);
}

Since Java 7, it is possible to catch multiple exceptions in one catch clause. However, these exceptions cannot be subclasses of each other. For example, IOException and FileNotFoundException cannot be used in one catch statement.

The following example will show how to pass the responsibility for handling exceptions to other methods.

ThrowingExceptions2.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ThrowingExceptions2 {
    
    public static void readFileContents(String fname) 
            throws FileNotFoundException, IOException {
        
        BufferedReader br = new BufferedReader(new FileReader(fname));        
        
        String line;
        while((line = br.readLine()) != null) {

            System.out.println(line);
        }                     
           
        br.close();
    }
    

    public static void main(String[] args) {
        
        try {
            readFileContents("quotes.txt");
            
        } catch (FileNotFoundException ex)  {
            
            Logger.getLogger(ThrowingExceptions2.class.getName()).log(Level.SEVERE, 
                    null, ex);        
            
        } catch (IOException ex) {
            
            Logger.getLogger(ThrowingExceptions2.class.getName()).log(Level.SEVERE, 
                    null, ex);            
        }           
    }
}

This example will read the contents of a text file. The responsibility to handle exceptions is delegated from the readFileContents() method to the main() method.

public static void readFileContents(String fname) 
        throws FileNotFoundException, IOException {

When we read from a file, we deal with two checked exceptions. The readFileContents() method throws both exceptions. The task to handle these exceptions is delegated to the caller.

try {
    readFileContents("quotes.txt");
    
} catch (FileNotFoundException ex)  {
    
    Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE, 
            null, ex);        
    
} catch (IOException ex) {
    
    Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE, 
            null, ex);            
}     

The main() method calls the readFileContents() method. The readFileContents() method throws two checked exceptions; therefore, the main() method must handle them.

Try with resources

The try-with-resources statement is a special kind of a try statement. It was introduced in Java 7. In parentheses we put one or more resources. These resources will be automatically closed at the end of the statement. We do not have to manually close the resources.

TryWithResources.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TryWithResources {

    public static void main(String[] args) {

        try (BufferedReader br = new BufferedReader(new FileReader("quotes"))) {

            String line;
            while ((line = br.readLine()) != null) {

                System.out.println(line);
            }
            
        } catch (FileNotFoundException ex) {

            Logger.getLogger(TryWithResources.class.getName()).log(Level.SEVERE,
                    null, ex);

        } catch (IOException ex) {

            Logger.getLogger(TryWithResources.class.getName()).log(Level.SEVERE,
                    null, ex);
        }
    }
}

In the example, we read the contents of a file and use the try-with-resources statement.

try (BufferedReader br = new BufferedReader(new FileReader("quotes"))) {

    String line;
    while ((line = br.readLine()) != null) {

        System.out.println(line);
    }
    
}

An opened file is a resource that must be closed. The input stream will be closed regardless of whether the try statement completes normally or abruptly.

Custom exception

Custom exceptions are user defined exception classes that extend either the Exception class or the RuntimeException class. The custom exception is cast off with the throw keyword.

BigValueExceptions.java
package com.zetcode;

class BigValueException extends Exception {

  public BigValueException(String message) {
      
        super(message);
    }
}

public class BigValueExceptions {

    public static void main(String[] args) {

        int x = 340004;
        final int LIMIT = 333;

        try {
            
            if (x > LIMIT) {

                throw new BigValueException("Exceeded the maximum value");
            }
            
        } catch (BigValueException e) {

            System.out.println(e.getMessage());
        }
    }
}

We assume that we have a situation in which we cannot deal with big numbers.

class BigValueException extends Exception {

  public BigValueException(String message) {
      
        super(message);
    }
}

We have a BigValueException class. This class derives from the built-in Exception class. It passes the error message to the parent class using the super keyword.

final int LIMIT = 333;

Numbers bigger than this constant are considered to be "big" by our program.

if (x > LIMIT) {

    throw new BigValueException("Exceeded the maximum value");
}

If the value is bigger than the limit, we throw our custom exception. We give the exception a message "Exceeded the maximum value".

} catch (BigValueException e) {

    System.out.println(e.getMessage());
}

We catch the exception and print its message to the console.

In this part of the Java tutorial, we have talked about exceptions in Java.