Chapter SEVEN
Error Handling and Exceptions


Exam Objectives

Handle exceptions using try/catch/finally, try-with-resources, and multi-catch blocks, including custom exceptions.

Chapter Content


Understanding Exceptions

Being able to run a program is not the same as running the program correctly. Any non-trivial program is susceptible to errors. Errors in user inputs, mathematical operations, and accessing resources, etc.

That’s where exceptions come into play. An exception indicates some sort of abnormal or exceptional condition that disrupts the normal flow of the program.

When an exceptional condition arises, an exception is said to be thrown. When this happens, the normal flow of the program is disrupted and the execution is transferred to a special block of code called an exception handler, if one exists for the exception.

The purpose of exceptions is to handle these errors gracefully and prevent the program from crashing or behaving unexpectedly. By using exceptions, you can separate the error-handling code from the normal program logic, making your code cleaner and more manageable.

Exceptions don’t necessarily stop the program entirely when they happen. When an exception is thrown, it is propagated up the call stack until it is caught and handled by an exception handler. If no suitable handler is found, the program will terminate. But if you have a try-catch block in place to handle the exception, your program can deal with the issue and continue running.

Understanding Exception Types

Most common exception classes in Java are subtypes of the java.lang.Exception class. But that’s not the whole story. To really understand exceptions, we need to look at the exception hierarchy:

                         ┌───────────┐
                         │ Throwable │
                         └─────┬─────┘
                               │
                   ┌───────────┴───────────┐
                   │                       │
           ┌───────────────┐       ┌───────────────┐
           │   Exception   │       │     Error     │
           └───────┬───────┘       └───────────────┘
                   │
       ┌───────────┴───────────┐
       │                       │
┌──────────────────┐   ┌───────────────────┐
│ RuntimeException │   │ Checked Exceptions│
└──────────────────┘   └───────────────────┘

At the top of the hierarchy is the java.lang.Throwable class. Beneath Throwable are two branches: Exception and Error. While they might sound similar, they actually represent quite different things in Java.

Exceptions are conditions that a reasonable application might want to catch and handle. They typically represent conditions that, while unusual, are not entirely unexpected. For example, trying to open a file that doesn’t exist would throw a FileNotFoundException.

Errors, on the other hand, are not meant to be caught or handled by your program. They indicate serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. For example, if your application runs out of memory, an OutOfMemoryError will be thrown.

Beneath the Exception class, there are two further categories: checked exceptions and unchecked exceptions (also known as runtime exceptions because they extend from java.lang.RuntimeException).

Checked exceptions are exceptional conditions that a well-written application should anticipate and handle. These are typically exceptions that are outside the control of the program, such as a file not being found, a network connection failing, or invalid user input. Checked exceptions are subclasses of Exception but not RuntimeException.

Unchecked exceptions are exceptional conditions that the application usually cannot anticipate or recover from. These usually indicate programming bugs, such as logic errors or improper use of an API. Unchecked exceptions are subclasses of RuntimeException.

While it might seem like unchecked exceptions are sufficient, checked exceptions serve an important purpose. They force the programmer to handle the exception, ensuring that proper error handling code is written. This leads to more robust and reliable code.

On the other hand, unchecked exceptions don’t need to be declared in a method’s throws clause if they can be thrown by the execution of the method. These usually represent defects in the program (bugs) and as such, the API client code cannot reasonably be expected to recover from them or to handle them in any way. Such exceptions most often indicate programming defects, and an unchecked exception is the way the Java programming language allows a developer to indicate a potential defect where the compiler cannot easily detect the problem.

Throwing an Exception

So far, we’ve talked about what exceptions are and the different types of exceptions. But how do exceptions actually get thrown?

An exception can be thrown in two ways: automatically by the Java runtime system, or explicitly by your code.

Many exceptions are thrown automatically by the Java runtime system. For example, if you try to access an array element with an index that is out of bounds, an ArrayIndexOutOfBoundsException will be thrown. If you try to divide a number by zero, an ArithmeticException will be thrown.

But you can also throw exceptions explicitly in your code using the throw statement. The general form of the throw statement is:

throw new ExceptionType(messageString);

Here, ExceptionType is the type of exception you want to throw, and messageString is an optional string that provides more information about the exception.

For example, let’s say you have a method that accepts an integer parameter age. If the passed-in age is negative, you might want to throw an exception:

public void checkAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    // rest of the method
}

In this case, we are throwing an IllegalArgumentException, which is a type of unchecked exception.

There are many cases where it’s appropriate, and even necessary, for you to throw exceptions in your own code.

By throwing exceptions, you can signal that an error has occurred and provide information about what went wrong. This is especially important when you’re writing methods or classes that will be used by other developers. By throwing exceptions, you can communicate to the user of your code that they have used your method or class incorrectly, or that something has gone wrong that they need to handle.

Moreover, by throwing exceptions, you can separate the error-handling code from the normal flow of your program. This makes your code more readable and maintainable.

Custom Exceptions

While Java provides a rich set of built-in exceptions, there are situations where it can be beneficial to create your own custom exceptions.

Custom exceptions allow you to add more context and meaning to the exceptions thrown by your application. They can help to better encapsulate the error conditions specific to your application domain.

For example, if you’re writing a library for parsing XML files, you might define a custom XMLFileParseException that you throw whenever there’s an error parsing an XML file. This communicates to the user of your library exactly what went wrong, as opposed to just throwing a generic Exception.

To create a custom checked exception, you simply need to extend the Exception class (or one of its subclasses):

public class XMLFileParseException extends Exception {
    public XMLFileParseException(String message) {
        super(message);
    }
}

To create a custom unchecked exception, you extend the RuntimeException class (or one of its subclasses):

public class InvalidInputException extends RuntimeException {
    public InvalidInputException(String message) {
        super(message);
    }
}

Then you can throw your custom exceptions just like any other exception:

throw new XMLFileParseException("Error parsing XML file: " + fileName);
throw new InvalidInputException("Input cannot be negative");

When deciding whether to make your custom exception checked or unchecked consider the following guidelines:

Another thing to consider when creating custom exceptions is serialization. If your exception class is going to be thrown across different JVMs (for example, in a distributed system), it should implement the java.io.Serializable interface.

public class RemoteServiceException extends Exception implements Serializable {
    // ...
}

This ensures that the exception object can be successfully serialized and deserialized when it’s transmitted across the network.

Finally, when creating custom exceptions, it’s a good practice to provide constructors that accept a message string and a cause exception. The cause is the exception that triggered your exception. This allows you to wrap lower-level exceptions in your higher-level custom exceptions, which can provide more context about the error.

public class DataAccessException extends Exception {
    public DataAccessException(String message) {
        super(message);
    }
    
    public DataAccessException(String message, Throwable cause) {
        super(message, cause);
    }
}

Then you can use it like this:

try {
    // some database operation that throws a SQLException
} catch (SQLException ex) {
    throw new DataAccessException("Error accessing database", ex);
}

This way, the caller of your code knows that a DataAccessException occurred, but can still access the underlying cause (the SQLException) if needed for more detailed error handling or logging.

Exceptions and Methods

When a method throws an exception, it must declare this in its method signature. This is done using the throws keyword followed by a list of the exceptions that the method might throw.

public void readFile(String fileName) throws FileNotFoundException {
    // code that might throw a FileNotFoundException
}

In this example, the readFile method declares that it might throw a FileNotFoundException.

However, not all exceptions need to be declared in the method signature. Only checked exceptions need to be declared. Unchecked exceptions (those that extend RuntimeException) do not need to be declared.

This brings us to an important distinction: the difference between throw and throws.

A useful analogy for remembering the difference is a baseball game:

Similarly in Java:

Overriding Methods with Exceptions

When you override a method in a subclass, you’re allowed to declare that the method throws fewer checked exceptions than the method you’re overriding.

class Parent {
    public void doSomething() throws IOException, SQLException {
        // ...
    }
}

class Child extends Parent {
    @Override
    public void doSomething() throws IOException {
        // ...
    }
}

In this example, the doSomething method in the Parent class declares that it can throw either an IOException or a SQLException. But when we override doSomething in the Child class, we declare that it only throws IOException.

This is allowed because it makes the method more usable. A caller of the Child class’s doSomething method only needs to handle IOException, not SQLException.

However, the reverse is not allowed. If the parent class’s method does not declare any exceptions, the overriding method in the child class cannot declare any checked exceptions.

class Parent {
    public void doSomething() {
        // ...
    }
}

class Child extends Parent {
    @Override
    public void doSomething() throws IOException {  // Compile-time error
        // ...
    }
}

This code will not compile because the overriding method (in Child) declares a checked exception (IOException) that the original method (in Parent) does not declare.

The rule is that an overriding method can declare to throw fewer exceptions or narrower exceptions (subclasses of the declared exceptions) than the original method, but not more or broader exceptions.

This rule exists to ensure that a child class can always be used in place of its parent class without causing any unexpected checked exceptions. This is a fundamental principle of polymorphism and inheritance in Java.

However, notice that this only applies for checked exceptions. Unchecked exceptions can be added freely when overriding methods.

Understanding Stack Traces

When an exception occurs in a Java program, it prints out a stack trace. A stack trace provides information about the exception and the state of the program when the exception occurred.

A stack trace can be incredibly useful when debugging a program. It tells you what went wrong and where in the code it went wrong.

Let’s look at an example stack trace:

Exception in thread "main" java.lang.NullPointerException
    at com.example.myproject.Book.getTitle(Book.java:16)
    at com.example.myproject.Author.getBookTitles(Author.java:25)
    at com.example.myproject.App.main(Bootstrap.java:14)

This stack trace is telling us that a NullPointerException occurred in the getTitle method of the Book class, which was called from line 25 of the getBookTitles method of the Author class, which in turn was called from line 14 of the main method of the App class.

Each line in the stack trace represents a method call, with the most recent call at the top. The first line shows the thrown exception, followed by the method calls on the stack at that time.

For each method call, the stack trace shows:

To read a stack trace, you can start from the top and work your way down. The top line tells you what type of exception was thrown. The lines after that represent the method calls on the stack, with the most recent call at the top.

Each line provides a clue about the program’s state when the exception was thrown. You can use these clues to pinpoint the location in your code where the problem occurred.

Recognizing Exception Classes

When you encounter an exception in your Java program, one of the first steps in resolving the issue is to identify what type of exception it is. Previously, you learned about the hierarchy of exception classes, each designed to represent a specific type of problem. Recognizing these classes and understanding when they’re thrown can help you diagnose issues more quickly.

Tip 1: Read the Exception Class Name The exception class name often indicates what went wrong. For example, a NullPointerException suggests that you’re trying to use a null reference, an ArrayIndexOutOfBoundsException indicates that you’re trying to access an array with an invalid index, and an IOException signals that something went wrong during an input/output operation.

Familiarizing yourself with the names and meanings of the most common exception classes can help you quickly identify issues in your code.

Tip 2: Understand the Exception Hierarchy Understanding the Java exception hierarchy can also aid in recognizing exceptions. All exceptions in Java inherit from the Throwable class, which has two main subclasses: Exception and Error.

Exceptions that inherit directly from the Exception class are checked exceptions. These typically represent issues that should be handled in your code. Common examples include IOException and SQLException

Exceptions that inherit from the RuntimeException class (which is a subclass of Exception) are unchecked exceptions. These often indicate programming errors, such as trying to access an array element with an out-of-bounds index (ArrayIndexOutOfBoundsException) or trying to use a null reference (NullPointerException).

Errors, on the other hand, represent serious problems that a reasonable application should not try to catch. These are typically irrecoverable conditions, such as running out of memory (OutOfMemoryError) or a stack overflow (StackOverflowError).

Tip 3: Read the Exception Message When an exception is thrown, it usually includes a message with more details about what went wrong. This message can be incredibly helpful in diagnosing the issue.

For example, consider this exception message:

java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5

This message indicates that the code is trying to access the element at index 10 in an array that only has 5 elements.

Tip 4: Look at the Stack Trace The stack trace that accompanies an exception can also provide valuable clues about what went wrong. The stack trace shows the sequence of method invocations that led to the exception.

Each line in the stack trace represents a method call, with the most recent call at the top. The line tells you the name of the method, the class it’s in, and the line number in the source code where the call occurred.

By tracing through the stack trace, you can often pinpoint the exact location in your code where the problem occurred.

Tip 5: Consult the Java API Documentation If you encounter an exception that you’re not familiar with, the Java API documentation can be a great resource. The documentation for each exception class provides information on when the exception is thrown and often includes examples of how to handle it.

For instance, the documentation for the ArrayIndexOutOfBoundsException states:

Thrown to indicate that an array has been accessed with an illegal index. The index is either negative or greater than or equal to the size of the array.

This provides a clear explanation of when you can expect to encounter this exception.

Finally, here’s a list of common unchecked and checked exceptions, as well as error classes:

Common Unchecked Exceptions Unchecked exceptions are those that are not checked at compile-time. They usually represent programming errors, such as logic mistakes or improper use of an API.

  1. ArithmeticException
    • Thrown when an exceptional arithmetic condition has occurred.
  2. ArrayIndexOutOfBoundsException
    • Thrown to indicate that an array has been accessed with an illegal index.
  3. ClassCastException
    • Thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance.
  4. IllegalArgumentException
    • Thrown to indicate that a method has been passed an illegal or inappropriate argument.
  5. IllegalStateException
    • Signals that a method has been invoked at an illegal or inappropriate time.
  6. NullPointerException
    • Thrown when an application attempts to use null in a case where an object is required.
  7. NumberFormatException
    • Thrown to indicate that the application has attempted to convert a string to one of the numeric types, but that the string does not have the appropriate format.

Common Checked Exceptions Checked exceptions are those that are checked at compile-time. They usually represent conditions that a reasonable application might want to catch.

  1. ClassNotFoundException
    • Thrown when an application tries to load a class through its string name but no definition for the class with the specified name could be found.
  2. IOException
    • Signals that an I/O exception of some sort has occurred.
  3. FileNotFoundException
    • Signals that an attempt to open the file denoted by a specified pathname has failed.
  4. InterruptedException
    • Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted.
  5. SQLException
    • An exception that provides information on a database access error or other errors.
  6. TimeoutException
    • Thrown when a blocking operation times out.

Common Error Classes Errors are usually thrown by the Java Virtual Machine and indicate serious problems that applications should not try to catch.

  1. OutOfMemoryError
    • Thrown when the JVM cannot allocate an object because it is out of memory.
  2. StackOverflowError
    • Thrown when a stack overflow occurs because an application recurses too deeply.
  3. VirtualMachineError
    • Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating.
  4. UnknownError
    • Thrown when an unknown but serious exception has occurred.

Handling Exceptions

When an exception occurs in your Java program, you need to handle it to prevent your program from abruptly terminating. This is done using try-catch blocks.

The try-catch Block

The basic syntax of a try-catch block is as follows:

try {
    // code that might throw an exception
} catch (ExceptionType e) {
    // code to handle the exception
}

You place the code that might throw an exception in the try block. If an exception occurs within the try block, it is caught by the catch block. The catch block specifies the type of exception it can handle (ExceptionType in the syntax above) and provides the code to handle that exception.

Here’s a concrete example:

try {
    File file = new File("example.txt");
    Scanner scanner = new Scanner(file);
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
    scanner.close();
} catch (FileNotFoundException e) {
    System.out.println("File not found: " + e.getMessage());
}

In this example, we’re trying to read from a file named example.txt. If the file doesn’t exist, a FileNotFoundException will be thrown. The catch block catches this exception and prints a message indicating that the file was not found.

You can use multiple catch blocks to handle different types of exceptions. If an exception occurs in the try block, Java will search for the first catch block that can handle the exception, starting from the top.

try {
    // code that might throw exceptions
} catch (IOException e) {
    // handle IOException
} catch (SQLException e) {
    // handle SQLException
}

In this example, if an IOException occurs in the try block, it will be handled by the first catch block. If a SQLException occurs, it will be handled by the second catch block.

You can also catch multiple exceptions in a single catch block. This is known as a multi-catch block.

try {
    // code that might throw exceptions
} catch (IOException | SQLException e) {
    // handle either IOException or SQLException
}

In this example, the catch block will handle either an IOException or a SQLException.

This can make your code more concise, but it should only be used when you want to handle the exceptions in the same manner. If you need to handle the exceptions differently, use separate catch blocks.

Also, the multi-catch block must catch two or more unrelated exceptions. Unrelated exceptions do not share a parent-child relationship in the exception hierarchy, like IOException and SQLException in the previous example.

However, it’s important to order your catch blocks from the most specific to the most general. This means placing catch blocks that catch subclasses of exceptions before those that catch their superclass exceptions. If you placed the IOException catch block before the FileNotFoundException catch block, the FileNotFoundException catch block would never be reached because FileNotFoundException is a subclass of IOException. As a result, the IOException catch block would catch all exceptions of type IOException, including FileNotFoundException, and the more specific handling code for FileNotFoundException would be bypassed.

For example:

try {
    // code that might throw exceptions
} catch (FileNotFoundException e) {
    // handle FileNotFoundException
} catch (IOException e) {
    // handle IOException
}

In this example, if a FileNotFoundException occurs, it will be caught by the first catch block. If a different IOException occurs, it will be caught by the second catch block. This ensures that specific exceptions are handled appropriately before more general exceptions.

The finally Block

The finally block is used to execute code that should always run, regardless of whether an exception was thrown or not:

try {
    // code that might throw an exception
} catch (ExceptionType e) {
    // handle the exception
} finally {
    // code that always runs
}

The finally block is often used for cleanup tasks, such as closing files or database connections.

If a return statement is executed inside the try block, the finally block will still execute before the method returns:

public static int returnTest() {
    try {
        return 1;
    } catch (Exception e) {
        return 2;
    } finally {
        System.out.println("Finally block");
    }
}

In this example, even though we’re returning from inside the try block, the finally block will still execute and print "Finally block" before the method returns.

The same is true if the return statement is in a catch block, the finally block will still execute before the method returns.

However, finally will not run if you call System.exit() in the try or catch block. System.exit() causes the Java Virtual Machine to exit, and the finally block will not be executed before the program terminates:

try {
    System.out.println("Try block");
    System.exit(0);
} catch (Exception e) {
    System.out.println("Catch block");
} finally {
    System.out.println("Finally block");
}

In this example, we call System.exit(0) in the try block, so it will only print "Try block" before the program terminates.

You can use a try block with a finally block and without any catch blocks.

try {
    // code that might throw an exception
} finally {
    // code that always runs
}

This can be useful when you want to ensure that certain code always runs, even if an exception is thrown, but you don’t actually want to handle the exception in this method.

Lastly, it’s possible for the finally block itself to throw an exception. If this happens, and there was also an exception in the try block, the exception from the finally block will be the one that’s actually thrown.

try {
    throw new Exception("Exception in try");
} finally {
    throw new Exception("Exception in finally");
}

In this example, the exception thrown in the finally block will be the one that’s actually thrown by the method. The exception from the try block will be suppressed.

Automating Resource Management with the try-with-resources Block

Introduced in Java 7, the try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement.

The basic syntax of a try-with-resources statement is:

try (Resource declaration) {
    // use the resource
} catch (ExceptionType e1) {
    // catch block
}

For a resource to be used in a try-with-resources statement, it must implement the java.lang.AutoCloseable interface. This interface has a single method, close(), which is called automatically at the end of the try block:

public interface AutoCloseable {
    void close() throws Exception;
}

Alternatively, resources can implement the java.io.Closeable interface:

public interface Closeable  extends AutoCloseable  {
    void close() throws IOException;
}

They both declare a close() method, and the only practical difference between these two interfaces is that the close method of the Closeable interface only throws exceptions of type IOException, while the close method of the AutoCloseable interface throws exceptions of type Exception (in other words, it can throw any kind of exception):

However, many of Java’s standard resources, such as Scanner, FileReader, and DatabaseConnection, already implement AutoCloseable.

Resources can be declared inside the parentheses of the try statement, separated by semicolons if there are multiple resources.

try (Scanner scanner = new Scanner(new File("example.txt"));
     PrintWriter writer = new PrintWriter(new File("output.txt"))) {
    // use the resources
}

The resources are declared so they can be closed without doing it explicitly in a finally block. Additionally, the resources declared in the try-with-resources statement are only in scope inside the try block. They are effectively final, meaning you cannot assign a new value to them after they have been initialized.

So, if the resources are declared outside the try-with-resources statement, they must be final:

final Scanner scanner = new Scanner(new File("example.txt"));
final PrintWriter writer = new PrintWriter(new File("output.txt"));

try (scanner; writer) {
    // use the resources
}

Or effectively final:

Scanner scanner = new Scanner(new File("example.txt"));
PrintWriter writer = new PrintWriter(new File("output.txt"));

// No reassignment after initialization makes them effectively final
try (scanner; writer) {
    // use the resources
}

If multiple resources are declared, they have to be separated by a semicolon and they are closed in the reverse order of their declaration. This is important if the resources depend on each other.

try (Scanner scanner = new Scanner(new File("example.txt"));
     DatabaseConnection connection = DriverManager.getConnection(DB_URL)) {
    // use the resources
}

In this example, connection will be closed before scanner.

As mentioned earlier, the resources declared in a try-with-resources statement are effectively final. While you don’t have to explicitly declare them as final, you cannot assign a new value to them after they have been initialized:

try (Scanner scanner = new Scanner(new File("example.txt"))) {
    scanner = new Scanner(new File("other.txt"));  // This will not compile
}

However, one thing to be aware of with try-with-resources is the possibility of suppressed exceptions.

Suppressed exceptions only occur when both the try block and the close() method throw exceptions.

If an exception is thrown from the try block and another exception is thrown from the automatic close() call, the exception from the close() call is suppressed. It is added as a suppressed exception to the exception thrown from the try block.

try (Scanner scanner = new Scanner(new File("example.txt"))) {
    throw new IllegalStateException("Thrown from try");
}

If the call to scanner.close() also throws an exception, that exception will be added as a suppressed exception to the IllegalStateException.

You can retrieve these suppressed exceptions by calling the Throwable[] java.lang.Throwable.getSuppressed() method on the exception thrown by the try block:

try (Scanner scanner = new Scanner(new File("example.txt"))) {
    throw new IllegalStateException("Thrown from try");
} catch (Exception e) {
    System.err.println(e.getMessage());
    Stream.of(e.getSuppressed())
        .forEach(t -> System.err.println(t.getMessage()));
}

This is the output (assuming the close() method throws an exception):

Thrown from try
Close Exception

Key Points

Practice Questions

1. Which of the following statements correctly describes a checked exception in Java?

A. A checked exception is a type of exception that inherits from the java.lang.RuntimeException class.
B. A checked exception must be either caught or declared in the method signature using the throws keyword.
C. A checked exception is an error that is typically caused by the environment in which the application is running, and it cannot be handled by the application.
D. A checked exception can be thrown by the Java Virtual Machine when a severe error occurs, such as an out-of-memory error.

2. Which of the following code snippets correctly defines and throws a custom checked exception?

public class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

public class TestCustomException {
    public static void main(String[] args) {
        try {
            methodThatThrowsException();
        } catch (CustomException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void methodThatThrowsException() throws CustomException {
        throw new CustomException("This is a custom checked exception");
    }
}

A. This code defines a custom checked exception and correctly throws and handles it.
B. This code defines a custom unchecked exception.
C. This code will not compile because the custom exception is not declared correctly in the method signature.
D. This code will compile but will not throw the custom exception at runtime.

3. Given the following class, what is the result?

public class Main {
    protected static int myMethod() {
        try {
            throw new RuntimeException();
        } catch(RuntimeException e) {
             return 1;
        } finally {
             return 2;
        }
    }
    public static void main(String[] args) {
        System.out.println(myMethod());
    }
}

A. 1
B. 2
C. Compilation fails
D. An exception occurs at runtime

4. Given the following class, which of the following statement is true?

public class Main {
    public static void main(String[] args) {
        try {
            // Do nothing
        } finally {
            // Do nothing
        }
    }
}

A. The code doesn’t compile correctly.
B. The code would compile correctly if we add a catch block.
C. The code would compile correctly if we remove the finally block.
D. The code compiles correctly as it is.

5. Which of the following statements are true? (Choose all that apply)

A. In a try-with-resources, the catch block is required.
B. The throw keyword is used to throw an exception.
C. In a try-with-resources block, if you declare more than one resource, they have to be separated by a semicolon.
D. If a catch block is defined for an exception that couldn’t be thrown by the code in the try block, a compile-time error is generated.

6. Given the following class, what is the result?:

class Connection implements java.io.Closeable {
    public void close() throws IOException {
        throw new IOException("Close Exception");
    }
}

public class Main {
    public static void main(String[] args) {
        try (Connection c = new Connection()) {
            throw new RuntimeException("RuntimeException");
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }
}

A. Close Exception
B. RuntimeException
C. RuntimeException and then CloseException
D. Compilation fails
E. The stack trace of an uncaught exception is printed

7. Which of the following exceptions are direct subclasses of RuntimeException?

A. java.io.FileNotFoundException
B. java.lang.ArithmeticException
C. java.lang.ClassCastException
D. java.lang.InterruptedException

8. Given the following code, what is the result?

class MyResource implements AutoCloseable {
    public void close() {
        throw new RuntimeException("Close Exception");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            throw new RuntimeException("Try Block Exception");
        } catch (RuntimeException e) {
            Throwable[] suppressed = e.getSuppressed();
            if (suppressed.length > 0) {
                for (Throwable t : suppressed) {
                    System.out.println("Suppressed: " + t.getMessage());
                }
            } else {
                System.out.println(e.getMessage());
            }
        }
    }
}

A. Only "Try Block Exception" is printed.
B. Only "Close Exception" is printed.
C. Both "Try Block Exception" and "Close Exception" are printed.
D. "Suppressed: Close Exception" is printed.

Do you like what you read? Would you consider?


Do you have a problem or something to say?

Report an issue with the book

Contact me