Zurück

Java Exceptions by Example


Christoph Gächter, Akadia AG, Information Technology, CH-3604 Thun
Phone: +41 33 335 86 21 / Fax: +41 33 335 86 25 /
EMail: christoph.gaechter@akadia.com


Overview

Java has a powerful concept for exception and error handling. An exception is an error that occurs at runtime. It is either generated by the Java Virtual Machine (VM) in response to an unexpected condition or it is generated by your code as a result of executing a throw statement.

Understanding Exceptions

Exceptions generated from runtime are called unchecked exceptions, since it is not possible for the compiler to determine that your code will handle the exception. Exception classes that descend from RuntimeException and Error classes are unchecked exceptions. Examples for RuntimeException are illegal cast operation, inappropriate use of a null pointer, referencing an out of bounds array element. Error exception classes signal critical problems that typically cannot be handled by your application. Examples are out of memory error, stack overflow, failure of the Java VM.

Thrown exceptions are referred to as checked exceptions. The compiler will confirm at compile time that the method includes code that might throw an exception. Moreover the compiler requires the code that calls such a method to include this call within a try block, and provide an appropriate catch block to catch the exception.

java.lang.Object
   |
   +--java.lang.Throwable
         |
         +--java.lang.Exception
         |     |
         |     +--java.lang.ClassNotFoundException
         |     |
         |     +--java.io.IOException
         |     |     |
         |     |     +--java.io.FileNotFoundException
         |     |
         |     +--java.lang.RuntimeException
         |           |
         |           +--java.lang.NullPointerException
         |           |
         |           +--java.lang.IndexOutOfBoundsException
         |                 |
         |                 +--java.lang.ArrayIndexOutOfBoundsException
         |
         +--java.lang.Error
               |
               +--java.lang.VirtualMachineError
                     |
                     +--java.lang.OutOfMemoryError

When an (either checked or unchecked) exception is thrown, execution will attempt to immediately branch to the first catch block whose associated exception class matches the class or a superclass of the thrown exception. If the exception does not occur within a try block or the thrown exception is not caught in a matching catch block, execution of the method immediately terminates and control returns to the invoker of the method, where this process is repeated. The result is that the exception chain is escalated until a matching catch block is found. If not, the thread containing the thrown exception is terminated.

First Example

The following Demo1 class demonstrates the behaviour of exceptions and applications.

import java.io.*;

class Demo1 {

  public static FileInputStream f1(String fileName)
    throws FileNotFoundException
  {
    FileInputStream fis = new FileInputStream(fileName);
    System.out.println("f1: File input stream created");
    return fis;
  }

  public static FileInputStream f2(String fileName)
  {
    FileInputStream fis = null;
    try
    {
      fis = new FileInputStream(fileName);
    }
    catch (FileNotFoundException ex)
    {
      System.out.println("f2: Oops, FileNotFoundException caught");
    }
    finally
    {
      System.out.println("f2: finally block");
    }
    System.out.println("f2: Returning from f2");
    return fis;
  }

  public static void main(String args[])
  {
    FileInputStream fis1 = null;
    FileInputStream fis2 = null;
    String fileName = "foo.bar";
    // String fileName = null;

    System.out.println(  "main: Starting " + Demo1.class.getName()
                       + " with file name = " + fileName);

    // get file input stream 1
    try {
      fis1 = f1(fileName);
    }
    catch (FileNotFoundException ex)
    {
      System.out.println("main: Oops, FileNotFoundException caught");
    }
    catch (Exception ex)
    {
      System.out.println("main: Oops, genreal exception caught");
    }

    // get file input stream 2
    fis2 = f2(fileName);

    System.out.println("main: " + Demo1.class.getName() + " ended");
  }
}

Compile and run the Demo1 class will generate output as follows (assuming that the file foo.bar does not exist):

main: Starting Demo1 with file name = foo.bar
main: Oops, FileNotFoundException caught
f2: Oops, FileNotFoundException caught
f2: finally block
f2: Returning from f2
main: Demo1 ended

The example program tries to create two file input streams. Therefore two methods f1 and f2 are implemented. First, the main program calls f1 method. Because the constructor of the FileInputStream throws a FileNotFoundException the method f1 must be defined with the throws FileNotFoundException in the method definition. The thrown exception is not handled in the method but forwarded to the invoker. Note, that the system output before the return statement is never executed.

So the invoker, in our example the main program, must catch this exception. The method f1 is called in a try block. The following catch blocks catch either a FileNotFoundException or a general Exception. In our example, the exception is caught in the first catch block and the system output is generated. Since the exception in f1 is caught and handled, the execution of the program is not terminated.

Second, the example program creates another FileInputStream invoking the f2 method. This method catches the FileNotFoundException, so this exception must not be forwarded to the invoker. The try ... catch statement is followed by a finally block. This block is always executed, regardless whether or not an exception occurs within the try block. This generates the system output in our example. Again, the exception is caught and the program executes, this generates the system output in f2 and main.

This was a straight forward example with caught exceptions. What happens with uncaught exceptions? Change the fileName assignment in the main method: Comment out the first assignment and activate the second String fileName = null; then compile and execute Demo1 again. The generated output is:

main: Starting Demo1 with file name = null
main: Oops, genreal exception caught
f2: finally block
java.lang.NullPointerException
java.io.FileInputStream Demo1.f2(java.lang.String)
void Demo1.main(java.lang.String[])
Exception in thread main

This time, we try to create two FileInputStream objects using null instead of a file name. Invoking f1 will generate a NullPointerException which is caught in the main program. Next f2 is called which could handle only the FileNotFoundException but not a NullPointerException. Nevertheless the finally block is executed and then the control returns to the main program. Because there is no try ... catch statement around the call to f2 and no matching catch block is found, the thread is terminated. Note, that f2 and main can not execute the system output statements any more.

Second Example

The second example will show some special behaviour in catch and finally blocks:

import java.io.*;

class Demo2 {

  public static FileInputStream f1(String fileName)
  {
    FileInputStream fis = null;
    try
    {
      fis = new FileInputStream(fileName);
    }
    catch (FileNotFoundException ex)
    {
      System.out.println("f1: Oops, FileNotFoundException caught");
      throw new Error("f1: File not found");
    }
    System.out.println("f1: File input stream created");
    return fis;
  }

  public static FileInputStream f2(String fileName)
  {
    FileInputStream fis = null;
    try
    {
      fis = new FileInputStream(fileName);
    }
    catch (FileNotFoundException ex)
    {
      System.out.println("f2: Oops, FileNotFoundException caught");
      return fis;
    }
    finally
    {
      System.out.println("f2: finally block");
      return fis;
    }

    // Compiler error: statement not reacheable
    // System.out.println("f2: Returning from f2");
    // return fis;

  }

  public static void main(String args[])
  {
    FileInputStream fis1 = null;
    FileInputStream fis2 = null;
    String fileName = "foo.bar";
    // String fileName = null;

    System.out.println(  "main: Starting " + Demo2.class.getName()
                       + " with file name = " + fileName);

    // get file input stream 1
    try {
      fis1 = f1(fileName);
    }
    catch (Exception ex)
    {
      System.out.println("main: Oops, general exception caught");
    }
    catch (Throwable th)
    {
      System.out.println("main: Oops, throwable object caught");
    }

    // get file input stream 2
    fis2 = f2(fileName);

    System.out.println("main: " + Demo2.class.getName() + " ended");
  }
}
}

Compile and run Demo2 will generate the following output:

main: Starting Demo2 with file name = foo.bar
f1: Oops, FileNotFoundException caught
main: Oops, throwable object caught
f2: Oops, FileNotFoundException caught
f2: finally block
main: Demo2 ended

Again f1 is called to get a new FileInputStream with a given file name. The thrown FileNotFoundException in f1 is caught in the following catch block. But this time an Error is thrown so that the method is terminated immediately. Because Error is not a subclass of Exception the first catch block does not match. But the second catch block matches all throwable objects.

Calling f2 method will generate a FileNotFoundException too. This exception is caught in f2 and the method returns directly from the catch block. Remember, that the finally block is executed regardless whether an exception is caught or not.

In both methods f1 and f2, the FileNotFoundException is caught and handled, so the program can terminate normally. Change again the fileName assignment in the main method and compile and run Demo2 again:

main: Starting Demo2 with file name = null
main: Oops, general exception caught
f2: finally block
main: Demo2 ended

The NullPointerException thrown in f1 is not caught, so the exception is forwarded to the invoker. The main program catches Exception, which is a superclass of the thrown exception.

The call to f2 method seems to work fine even the thrown NullPointerException is not caught directly. Note, that the finally block is executed regardless whether an exception is caught or not. Since the finally block terminates with a return statement, the program executes without failure. So be careful returning within finally blocks, this breaks the exception chaining to the invoker and it simulates error free program execution.

Summary

  • Normal program execution is immediately branched when an exception is thrown.
     

  • Checked exceptions must be caught or forwarded. This can be done in a try ... catch statement or by defining the exception in the method definition.
     

  • The exception is caught by the first catch block whose associated exception class matches the class or a superclass of the thrown exception.
     

  • If no matching catch block is found in the exception chain, the thread containing the thrown exception is terminated.
     

  • The finally block after a try ... catch statement is executed regardless whether an exception is caught or not.
     

  • Returning within a finally block breaks the exception chain to the invoker even for uncaught exceptions.