Java Fundamentals Tutorial: Exceptions

9. Exceptions

Exception Handling in Java

9.1. What are Exceptions?

  • Exceptions are events that occur during the execution of programs that disrupt the normal flow of instructions (e.g. divide by zero, array access out of bound, etc.).
  • In Java, an exception is an object that wraps an error event that occurred within a method and contains:

    • Information about the error including its type
    • The state of the program when the error occurred
    • Optionally, other custom information
  • Exception objects can be thrown and caught.

Exceptions are used to indicate many different types of error conditions.

  • JVM Errors:

    • OutOfMemoryError
    • StackOverflowError
    • LinkageError

System errors:

  • FileNotFoundException
  • IOException
  • SocketTimeoutException

    • Programming errors:
  • NullPointerException
  • ArrayIndexOutOfBoundsException
  • ArithmeticException

9.2. Why Use Exceptions?

  • Exceptions separate error handling code from regular code.

    • Benefit: Cleaner algorithms, less clutter
  • Exceptions propagate errors up the call stack.

    • Benefit: Nested methods do not have to explicitly catch-and-forward errors (less work, more reliable)
  • Exception classes group and differentiate error types.

    • You can group errors by their generalize parent class, or
    • Differentiate errors by their actual class
  • Exceptions standardize error handling.

In a traditional programming language like C, an error condition is usually indicated using an integer return value (e.g. -1 for out of memory). However, this practice:

  • Requires standardization on error codes (hard for large projects)
  • Makes functions hard to use because they must return actual values by reference
  • Requires programmers to check return error codes in every nested function call. This leads to cluttered source code and hard-to-follow logic
  • Does not allow for the state of the program to be easily captured on an error condition for later examination
  • Cannot be enforced, and programmers often ignore error conditions, which leads to security or stability issues in programs

9.3. Built-In Exceptions

  • Many exceptions and errors are automatically generated by the Java virtual machine.
  • Errors, generally abnormal situations in the JVM, such as:

    • Running out of memory
    • Infinite recursion
    • Inability to link to another class
  • Runtime exceptions, generally a result of programming errors, such as:

    • Dereferencing a null reference
    • Trying to read outside the bounds of an array
    • Dividing an integer value by zero

public class ExceptionDemo {

    public static void main (String[] args) {

    private static int divideArray(String[] array) {
        String s1 = array[0];
        String s2 = array[1];
        return divideStrings(s1, s2);

    private static int divideStrings(String s1, String s2) {
        int i1 = Integer.parseInt(s1);
        int i2 = Integer.parseInt(s2);
        return divideInts(i1, i2);

    private static int divideInts(int i1, int i2) {
        return i1 / i2;

Compile and try executing the program as follows:

java ExceptionDemo 100 4
java ExceptionDemo 100 0
java ExceptionDemo 100 four
java ExceptionDemo 100

Note that we could have overloaded all divide*() methods (since they have different parameters), but this way the generated stack traces are easier to follow.

9.4. Exception Types

Figure 7. A Sample of Exception Types

A Sample of Exception Types

All exceptions and errors extend from a common java.lang.Throwable parent class. Only Throwable objects can be thrown and caught.

Other classes that have special meaning in Java are java.lang.Error (JVM Error), java.lang.Exception (System Error), and java.lang.RuntimeException (Programming Error).

public class java.lang.Throwable extends Object
            implements {
    public Throwable();
    public Throwable(String msg);
    public Throwable(String msg, Throwable cause);
    public Throwable(Throwable cause);
    public String getMessage();
    public String getLocalizedMessage();
    public Throwable getCause();
    public Throwable initCause(Throwable cause);
    public String toString();
    public void printStackTrace();
    public void printStackTrace(;
    public void printStackTrace(;
    public Throwable fillInStackTrace();
    public StackTraceElement[] getStackTrace();
    public void setStackTrace(StackTraceElement[] stackTrace);

9.5. Checked vs. Unchecked Exceptions

  • Errors and RuntimeExceptions are unchecked — that is, the compiler does not enforce (check) that you handle them explicitly.
  • Methods do not have to declare that they throw them (in the method signatures).
  • It is assumed that the application cannot do anything to recover from these exceptions (at runtime).
  • All other Exceptions are checked — that is, the compiler enforces that you handle them explicitly.
  • Methods that generate checked exceptions must declare that they throw them.
  • Methods that invoke other methods that throw checked exceptions must either handle them (they can be reasonably expected to recover) or let them propagate by declaring that they throw them.

There is a lot of controversy around checked vs. unchecked exceptions.

Checked exceptions give API designers the power to force programmers to deal with the exceptions. API designers expect programmers to be able to reasonably recover from those exceptions, even if that just means logging the exceptions and/or returning error messages to the users.

Unchecked exceptions give programmers the power to ignore exceptions that they cannot recover from, and only handle the ones they can. This leads to less clutter. However, many programmers simply ignore unchecked exceptions, because they are by default un-recoverable. Turning all exceptions into the unchecked type would likely lead to poor overall error handling.

It is interesting to note that Microsoft’s C# programming language (largely based on Java) does not have a concept of checked exceptions. All exception handling is purely optional.

9.6. Exception Lifecycle

  • After an exception object is created, it is handed off to the runtime system (thrown).
  • The runtime system attempts to find a handler for the exception by backtracking the ordered list of methods that had been called.

    • This is known as the call stack.
  • If a handler is found, the exception is caught.

    • It is handled, or possibly re-thrown.
  • If the handler is not found (the runtime backtracks all the way to the main() method), the exception stack trace is printed to the standard error channel (stderr) and the application aborts execution.

For example:

java ExceptionDemo 100 0

Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo.divideInts(
at ExceptionDemo.divideStrings(
at ExceptionDemo.divideArray(
at ExceptionDemo.main(

Looking at the list bottom-up, we see the methods that are being called from main() all the way up to the one that resulted in the exception condition. Next to each method is the line number where that method calls into the next method, or in the case of the last one, throws the exception.

9.7. Handling Exceptions

Figure 8. The `try-catch-finally` Control Structure

The `try-catch-finally` Control Structure

public class ExceptionDemo {

    public static void main (String[] args) {

    private static void divideSafely(String[] array) {
        try {
        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("Usage: ExceptionDemo <num1> <num2>");
        } catch (NumberFormatException e) {
            System.err.println("Args must be integers");
        } catch (ArithmeticException e) {
            System.err.println("Cannot divide by zero");

    private static int divideArray(String[] array) {
        String s1 = array[0];
        String s2 = array[1];
        return divideStrings(s1, s2);

    private static int divideStrings(String s1, String s2) {
        int i1 = Integer.parseInt(s1);
        int i2 = Integer.parseInt(s2);
        return divideInts(i1, i2);

    private static int divideInts(int i1, int i2) {
        return i1 / i2;

9.8. Handling Exceptions (cont.)

  • The try-catch-finally structure:

    try {
        // Code bock
    catch (ExceptionType1 e1) {
        // Handle ExceptionType1 exceptions
    catch (ExceptionType2 e2) {
        // Handle ExceptionType2 exceptions
    // ...
    finally {
        // Code always executed after the
        // try and any catch block
    • Both catch and finally blocks are optional, but at least one must follow a try.
    • The try-catch-finally structure can be nested in try, catch, or finally blocks.
    • The finally block is used to clean up resources, particularly in the context of I/O.
    • If you omit the catch block, the finally block is executed before the exception is propagated.
  • Exceptions can be caught at any level.
  • If they are not caught, they are said to propagate to the next method.

public class ExceptionDemo {

    public static void main (String[] args) {

    private static void divideSafely(String[] array) {
        try {
        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("Usage: ExceptionDemo <num1> <num2>");

    private static int divideArray(String[] array) {
        String s1 = array[0];
        String s2 = array[1];
        return divideStrings(s1, s2);

    private static int divideStrings(String s1, String s2) {
        try {
            int i1 = Integer.parseInt(s1);
            int i2 = Integer.parseInt(s2);
            return divideInts(i1, i2);
        } catch (NumberFormatException e) {
            return 0;

    private static int divideInts(int i1, int i2) {
        try {
            return i1 / i2;
        } catch (ArithmeticException e) {
            return 0;

9.9. Grouping Exceptions

  • Exceptions can be caught based on their generic type. For example:

    catch (RuntimeException e) {
        // Handle all runtime errors

Be careful about being too generic in the types of exceptions you catch. You might end up catching an exception you didn’t know would be thrown and end up “hiding” programming errors you weren’t aware of.

  • catch statements are like repeated else-ifs.

    • At most one catch bock is executed.
    • Be sure to catch generic exceptions after the specific ones.

public class ExceptionDemo {

    public static void main (String[] args) {

    private static void divideSafely(String[] array) {
        try {
        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("Usage: ExceptionDemo <num1> <num2>");
        } catch (RuntimeException e) {
            System.err.println("Error result: 0");

    private static int divideArray(String[] array) {
        String s1 = array[0];
        String s2 = array[1];
        return divideStrings(s1, s2);

    private static int divideStrings(String s1, String s2) {
        int i1 = Integer.parseInt(s1);
        int i2 = Integer.parseInt(s2);
        return divideInts(i1, i2);

    private static int divideInts(int i1, int i2) {
        return i1 / i2;

9.10. Throwing Exception

  • A method that generates an unhandled exception is said to throw an exception. That can happen because:

    • It generates an exception to signal an exceptional condition, or
    • A method it calls throws an exception
  • Declare that your method throws the exception using the throws clause.

    • This is required only for checked exceptions.
  • Recommended: Document (in JavaDoc) that the method @throws the exception and under what circumstances.
  • To generate an exceptional condition:

    • Create an exception of the appropriate type. Use predefined exception types if possible, or create your own type if appropriate.
    • Set the error message on the exception, if applicable.
    • Execute a throw statement, providing the created exception as an argument.

Some of the more commonly used exception types are:

Parameter value is null where prohibited
Non-null parameter value is inappropriate
Object state is inappropriate for method invocation
Index parameter is out of range
Object does not support the method

For example:

* Circle shape
public class Circle implements Shape {
    private final double radius;
    * Constructor.
    * @param radius the radius of the circle.
    * @throws IllegalArgumentException if radius is negative.
    public Circle(double radius) {
        if (radius < 0.0) {
            throw new IllegalArgumentException("Radius " + radius
                + " cannot be negative");
        this.radius = radius;

9.11. Creating an Exception Class

  • If you wish to define your own exception:

    • Pick a self-describing *Exception class name.
    • Decide if the exception should be checked or unchecked.

      extends Exception
      extends RuntimeException
    • Define constructor(s) that call into super’s constructor(s), taking message and/or cause parameters.
    • Consider adding custom fields, accessors, and mutators to allow programmatic introspection of a thrown exception, rather than requiring code to parse an error message.

 * Exception thrown to indicate that there is insufficient
 * balance in a bank account.
 * @author me
 * @version 1.0
public class InsufficientBalanceException extends Exception {
    private final double available;
    private final double required;

     * Constructor.
     * @param available available account balance
     * @param required required account balance
    public InsufficientBalanceException(double available, double required) {
        super("Available $"+available+" but required $"+required);
        this.available = available;
        this.required = required;

     * Get available account balance
     * @return available account balance
    public double getAvailable() {
        return available;

     * Get required account balance
     * @return required account balance
    public double getRequired() {
        return required;

     * Get the difference between required and available account balances
     * @return required - available
    public double getDifference() {
        return required - available;

9.12. Nesting Exceptions

  • A Throwable object can nest another Throwable object as its cause.
  • To make APIs independent of the actual implementation, many methods throw generic exceptions. For example:

    • Implementation exception: SQLException
    • API exception: DataAccessException
    • API exception nests implementation exception
    • To get the original implementation exception, do apiException.getCause()

public Customer getCustomer(String id) throws DataAccessException {
    try {
        Connection con = this.connectionFactory.getConnection();
        try {
            String sql
                = "SELECT * FROM Customers WHERE CustomerID='"
                + id + "'";
            PreparedStatement stmt = con.prepareStatement(sql);
            try {
                ResultSet result = stmt.executeQuery(sql);
                try {
                    return ? this.readCustomer(result) : null;
                } finally {
            } finally {
        } finally {
    } catch (SQLException e) {
        throw new DataAccessException("Failed to get customer ["
                        + id + "]: " + e.getMessage(), e);

Observe that the getCustomer() method signature does not say anything about customers being retrieved from a SQL-based RDBMS. This allows us to provide an alternative implementation, say based on a LDAP directory server or XML files.

Table of Contents

1. Overview of Java
1.1. History of Java
1.2. What is Java?
1.3. Why Java?
1.4. Java After 15 years
2. A Java Hello World Program
2.2. Java Keywords
2.3. Java Identifiers
2.4. Compiling Java Programs
2.5. Running Java Programs
2.6. Comments in Java Code
2.7. The main() Method
3. Data Types
3.1. Declaring and Assigning Variables
3.2. Java Primitive Types
3.3. Conversion Between Types
3.4. Declaring Arrays
3.5. Creating and Initializing Array Objects
3.6. Modifying Array Size
3.7. Strings
4. Operators
4.1. Arithmetic Operators
4.2. Shortcut Arithmetic Operators
4.3. String Concatenation
4.4. Relational Operators
4.5. Logical Boolean Operators
4.6. Bitwise Operators
4.7. Assignment Operators
4.8. Other Operators
4.9. Operator Precedence
5. Statements and Flow Control
5.1. Expressions
5.2. Statements and Blocks
5.3. Local Variable Storage: The Stack
5.4. The return Statement
5.5. The if-else Statement
5.6. The switch Statement
5.7. The switch Statement (cont.)
5.8. The while Loop Statement
5.9. The for Loop Statement
5.10. Using for to Iterate over Arrays and Collections
5.11. The break Statement
5.12. The continue Statement
5.13. Nested Loops and Labels
6. Object Oriented Programming in Java
6.1. What is Object Oriented Programming (OOP)?
6.2. Why OOP?
6.3. Class vs. Object
6.4. Classes in Java
6.5. Objects in Java
6.6. Java Memory Model
6.7. Accessing Objects through References
6.8. Garbage Collection
6.9. Methods in Java
6.10. Methods in Java (cont.)
6.11. Method Declarations
6.12. Method Signatures
6.13. Invoking Methods
6.14. Static vs. Instance Data Fields
6.15. Static vs. Instance Methods
6.16. Method Overloading
6.17. Variable Argument Length Methods
6.18. Constructors
6.19. Constructors (cont.)
6.20. Constants
6.21. Encapsulation
6.22. Access Modifiers: Enforcing Encapsulation
6.23. Accessors (Getters) and Mutators (Setters)
6.24. Inheritance
6.25. Inheritance, Composition, and Aggregation
6.26. Inheritance in Java
6.27. Invoking Base Class Constructors
6.28. Overriding vs. Overloading
6.29. Polymorphism
6.30. More on Upcasting
6.31. Downcasting
6.32. Abstract Classes and Methods
6.33. Interfaces
6.34. Defining a Java Interface
6.35. Implementing a Java Interface
6.36. Polymorphism through Interfaces
6.37. Object: Java’s Ultimate Superclass
6.38. Overriding Object.toString()
6.39. Object Equality
6.40. Object Equivalence
6.41. Object Equivalence (cont.)
7. Packaging
7.1. Why is Packaging Needed?
7.2. Packages in Java
7.3. Sub-Packages in Java
7.4. Package Naming Conventions
7.5. Using Package Members: Qualified Names
7.6. Importing Package Members
7.7. Static Imports
7.8. Access Modifiers and Packages
7.9. The Class Path
7.10. Java Archive (JAR) Files
7.11. The jar Command-Line Tool Examples
8. JavaDoc
8.1. What is JavaDoc?
8.2. Reading Doc Comments (Java API)
8.3. Defining JavaDoc
8.4. Doc Comment Tags
8.5. Generating Doc Comment Output
9. Exceptions
9.1. What are Exceptions?
9.2. Why Use Exceptions?
9.3. Built-In Exceptions
9.4. Exception Types
9.5. Checked vs. Unchecked Exceptions
9.6. Exception Lifecycle
9.7. Handling Exceptions
9.8. Handling Exceptions (cont.)
9.9. Grouping Exceptions
9.10. Throwing Exception
9.11. Creating an Exception Class
9.12. Nesting Exceptions
10. The java.lang Package
10.1. Primitive Wrappers
10.2. String and StringBuilder
10.3. The Math Class
10.4. The System Class
11. Input/Output
11.1. Representing File Paths
11.2. Managing File Paths
11.3. Input/Output Class Hierarchy
11.4. Byte Streams
11.5. Character Streams
11.6. Exception Handling in Java I/O
11.7. Java File I/O Classes
11.8. Easy Text Output: PrintWriter/PrintStream
11.9. Reading from the Terminal
11.10. Filtered Streams
11.11. Object Serialization
12.2. URL/Connections
12.3. TCP Sockets
13. Java Collections and Generics
13.1. The Java Collections Framework
13.2. The Collection Interface
13.3. Iterating Over a Collection
13.4. The List Interface
13.5. Classes Implementing List
13.6. The Set and SortedSet Interfaces
13.7. Classes Implementing Set
13.8. The Queue Interface
13.9. The Map Interface
13.10. Retrieving Map Views
13.11. Classes Implementing Map
13.12. The Collections Class
13.13. Type Safety in Java Collections
13.14. Java Generics
13.15. Generics and Polymorphism
13.16. Type Wildcards
13.17. Qualified Type Wildcards
13.18. Generic Methods
14. Threading
14.1. Runnable And Thread
14.2. Thread Synchronization
14.3. More Thread Synchronization
14.4. Java 5 Concurrency Features
14.5. Thread Scheduling Pre-Java 5
14.6. Thread Scheduling In Java 5
14.7. Java 5’s ExecutorService
14.8. Getting Results From Threads Pre Java 5
14.9. Getting Results From Threads In Java 5
14.10. Thread Sync Pre-Java 5
14.11. Thread Sync In Java 5 With Locks
14.12. Benefits Of Java 5 Locks
14.13. Java 5 Conditions
14.14. Atomics In Java 5
14.15. Other Java 5 Concurrency Features
15. Additional Java Features
15.1. The Date Class
15.2. The Calendar Class
15.3. The TimeZone Class
15.4. Formatting and Parsing Dates
15.5. Formatting and Parsing Dates
15.6. Typesafe Enums
15.7. Typesafe Enums (cont.)
15.8. EnumSet and EnumMap
15.9. Annotations
15.10. Using Annotations
15.11. Standard Java Annotations
16. Java Native Interface (JNI)
16.1. JNI Overview
16.2. JNI Components
16.3. JNI Development (Java)
16.4. JNI Development (C)
16.5. JNI Development (Compile)
16.6. Type Conversion
16.7. Native Method Arguments
16.8. String Conversion
16.9. Array Conversion
16.10. Throwing Exceptions In The Native World
16.11. Access Properties And Methods From Native Code
17. java.sql
17.1. JDBC Overview
17.2. JDBC Drivers
17.3. Getting a JDBC Connection
17.4. Preparing a Statement
17.5. Processing ResultSet
17.6. Using ResultSetMetaData
17.7. Updates
18. XML Processing
18.1. Parsing XML with Java
18.2. Event-Driven Approach
18.3. Overview of SAX
18.4. Parsing XML with SAX
18.5. Object-Model Approach
18.6. Overview of DOM
18.7. Parsing XML with DOM
19. Design Patterns
19.1. What are Design Patterns?
19.2. Singleton
19.3. Factory Method
19.4. Abstract Factory
19.5. Adapter
19.6. Composite
19.7. Decorator
19.8. Chain of Responsibility
19.9. Observer / Publish-Subscribe
19.10. Strategy
19.11. Template
19.12. Data Access Object