Search Tutorials


1Z0-830 Java SE 21 - Exception Handling | JavaInUse

1Z0-830 Exception Handling - Java SE 21 Certification Prep

Exception handling is one of the most heavily tested topics on the 1Z0-830 exam. This guide covers everything you need to know: the exception class hierarchy, try-catch-finally behavior, multi-catch syntax rules, try-with-resources, throwing and declaring exceptions, custom exception design, and common exam traps.

Exception Hierarchy

All exceptions in Java descend from Throwable. The two main branches are Error and Exception. Understanding which exceptions are checked versus unchecked is critical for the exam.
Exception Class Hierarchy:
Throwable
  |-- Error (unchecked - do not catch these)
  |     |-- OutOfMemoryError
  |     |-- StackOverflowError
  |
  |-- Exception
        |-- RuntimeException (unchecked - no declaration required)
        |     |-- NullPointerException
        |     |-- ArrayIndexOutOfBoundsException
        |     |-- IllegalArgumentException
        |     |-- ArithmeticException
        |     |-- ClassCastException
        |     |-- NumberFormatException
        |     |-- IllegalStateException
        |
        |-- IOException (checked - must handle or declare)
        |-- SQLException (checked - must handle or declare)
        |-- ClassNotFoundException (checked - must handle or declare)
Checked exceptions extend Exception but not RuntimeException. The compiler forces you to either catch them or declare them with throws. Unchecked exceptions extend RuntimeException or Error and require no compile-time handling. Error subclasses represent serious JVM-level failures and should never be caught in application code.
// Checked exception - compiler enforces handling or declaration
public void readFile() throws IOException {
    FileReader fr = new FileReader("file.txt");  // IOException is checked
}

// Unchecked exception - no compile-time requirement to handle
public void divide(int a, int b) {
    int result = a / b;  // Throws ArithmeticException if b == 0
}

// Frequently tested unchecked exceptions:
// NullPointerException       - calling a method on a null reference
// ArrayIndexOutOfBoundsException - accessing index outside array bounds
// ClassCastException         - casting an object to an incompatible type
// IllegalArgumentException   - passing an invalid argument to a method
// IllegalStateException      - calling a method when the object is in the wrong state
// NumberFormatException      - parsing a string that is not a valid number
//                              (subclass of IllegalArgumentException)

Try-Catch-Finally

A try block must be followed by at least one catch block, one finally block, or both. The finally block always executes regardless of whether an exception was thrown or caught. This makes it the right place for cleanup code such as closing connections or releasing locks.
// Standard try-catch-finally
try {
    int result = divide(10, 0);
} catch (ArithmeticException e) {
    // Handles only ArithmeticException
    System.out.println("Cannot divide by zero");
} catch (Exception e) {
    // Handles all other Exception subclasses
    // Must come AFTER more specific catch blocks
    System.out.println("Error: " + e.getMessage());
} finally {
    // Executes always - even if a return statement runs in try or catch
    System.out.println("Cleanup");
}

// Useful methods on the exception object
try {
    riskyOperation();
} catch (Exception e) {
    e.getMessage();        // Returns the detail message string
    e.getCause();          // Returns the original cause (if wrapped)
    e.printStackTrace();   // Prints the full stack trace to stderr
    e.getStackTrace();     // Returns the stack trace as a StackTraceElement array
}

// Valid try block combinations:
try { } catch (Exception e) { }                  // OK - catch only
try { } finally { }                              // OK - finally only
try { } catch (Exception e) { } finally { }      // OK - catch and finally
// try { }                                       // Compile error - neither catch nor finally
Exam Tip: The finally block executes even if the try or catch block contains a return statement. The only situations where finally does NOT execute are: a call to System.exit(), a JVM crash, or an infinite loop inside the try block that never terminates. If the finally block itself has a return statement, it overrides any return value from the try or catch block.

Multi-Catch

Introduced in Java 7, multi-catch allows you to handle multiple unrelated exception types in a single catch block using the pipe character. This reduces code duplication when the handling logic is the same for different exceptions.
// Multi-catch syntax - handle two unrelated exceptions the same way
try {
    // Code that may throw IOException or SQLException
} catch (IOException | SQLException e) {
    System.out.println("Error: " + e.getMessage());
}

// Rule 1: Exceptions in a multi-catch cannot be in a parent-child relationship
try {
    // ...
} catch (Exception | IOException e) {  // Compile error!
    // IOException is a subclass of Exception - redundant and illegal
}

// Rule 2: The exception variable in a multi-catch is effectively final
try {
    // ...
} catch (IOException | SQLException e) {
    e = new IOException();  // Compile error - cannot reassign e
}

// Rule 3: Catch blocks must be ordered from most specific to most general
try {
    // ...
} catch (FileNotFoundException e) {
    // FileNotFoundException is more specific - must come first
} catch (IOException e) {
    // IOException is the parent - comes after
} catch (Exception e) {
    // Most general - must be last
}

// This does NOT compile - FileNotFoundException is unreachable after IOException:
// catch (IOException e) { }
// catch (FileNotFoundException e) { }  // Compile error: already caught by IOException
Exam Tip: On the exam, watch for catch blocks that list a parent class before a child class. This is a compile error. Also watch for multi-catch blocks that include related exceptions such as Exception | IOException - these are also compile errors.

Try-With-Resources

Try-with-resources, introduced in Java 7, automatically closes any resource declared in the try header once the block exits, whether normally or via an exception. A resource must implement the AutoCloseable interface. Resources are closed in reverse order of declaration, which is the opposite of the order they were opened.
// Resources declared in the try header are closed automatically
try (FileReader fr = new FileReader("file.txt");
     BufferedReader br = new BufferedReader(fr)) {
    String line = br.readLine();
} // br is closed first, then fr (reverse declaration order)

// AutoCloseable - the base interface for try-with-resources
public interface AutoCloseable {
    void close() throws Exception;
}

// Closeable - extends AutoCloseable for I/O resources specifically
public interface Closeable extends AutoCloseable {
    void close() throws IOException;  // Narrower checked exception
}

// Custom resource implementing AutoCloseable
public class MyResource implements AutoCloseable {
    public void doWork() {
        System.out.println("Working...");
    }

    @Override
    public void close() {
        System.out.println("Closing resource");
    }
}

try (MyResource r = new MyResource()) {
    r.doWork();
}
// Output:
// Working...
// Closing resource

// Java 9+ - use effectively final variables declared outside the try
FileReader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr);
try (fr; br) {  // fr and br must not be reassigned after declaration
    String line = br.readLine();
}

// Suppressed exceptions - what happens when both try and close() throw
try (MyResource r = new MyResource()) {
    throw new RuntimeException("Exception in try block");
}
// If close() also throws, that exception is suppressed (not lost).
// You can retrieve suppressed exceptions from the primary exception:
// Throwable[] suppressed = primaryException.getSuppressed();
Exam Tip: Resources are closed in REVERSE order of declaration. If the try block throws an exception and close() also throws, the try block exception is the one that propagates. The close() exception is attached as a suppressed exception and can be retrieved via getSuppressed(). This is the opposite behavior from a finally block, where the finally exception would replace the original one.

Throwing Exceptions

Java uses two keywords for exceptions: throw to actually throw an exception instance at runtime, and throws to declare in a method signature that the method may propagate a checked exception to its caller. Understanding this distinction and how overriding interacts with declared exceptions is a common exam focus.
// throw - used to throw an exception object at runtime
public void validate(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative: " + age);
    }
}

// throws - used in the method signature to declare checked exceptions
public void readFile(String path) throws IOException {
    FileReader fr = new FileReader(path);
}

// A method can declare multiple checked exceptions
public void process() throws IOException, SQLException {
    // ...
}

// Overriding rules for declared exceptions (frequently tested)
class Parent {
    public void method() throws IOException { }
}

class Child extends Parent {
    // ALLOWED: throw the same exception
    public void method() throws IOException { }

    // ALLOWED: throw a subclass of the declared exception
    // public void method() throws FileNotFoundException { }

    // ALLOWED: declare no exception at all
    // public void method() { }

    // NOT ALLOWED: throw a broader checked exception
    // public void method() throws Exception { }  // Compile error

    // ALLOWED: throw any unchecked exception regardless of parent declaration
    // public void method() throws RuntimeException { }  // OK
}
Exam Tip: When overriding a method, the overriding method can only throw checked exceptions that are the same as or narrower than those declared in the parent method. It can always throw unchecked exceptions. It can also choose to declare fewer or no checked exceptions, since removing a throws clause never breaks the contract.

Custom Exceptions

You can create your own exception classes by extending either Exception (for checked exceptions) or RuntimeException (for unchecked exceptions). Always provide constructors for the message string and for wrapping a cause, so that exception chaining works correctly and diagnostic information is not lost.
// Custom checked exception - callers must handle or declare it
public class BusinessException extends Exception {
    public BusinessException() {
        super();
    }

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

    // Always include a cause constructor to support exception chaining
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

// Custom unchecked exception - no handling required by callers
public class ValidationException extends RuntimeException {
    private final String field;  // Custom field to carry extra context

    public ValidationException(String field, String message) {
        super(message);
        this.field = field;
    }

    public String getField() {
        return field;
    }
}

// Using custom exceptions in application code
public void createUser(String email) throws BusinessException {
    if (email == null || email.isBlank()) {
        throw new ValidationException("email", "Email is required");
    }
    if (userExists(email)) {
        throw new BusinessException("User already exists: " + email);
    }
}

// Exception chaining - wrap a low-level exception in a higher-level one
// This preserves the original cause for debugging while presenting a
// meaningful exception type to the caller
try {
    connectToDatabase();
} catch (SQLException e) {
    throw new BusinessException("Database connection failed", e);
}
Exam Tip: Custom checked exceptions extend Exception. Custom unchecked exceptions extend RuntimeException. Always include constructors for both a message and a cause. Exception chaining using the cause constructor is the correct way to wrap low-level exceptions without losing the original stack trace.

Common Exception Patterns

The exam tests several behavioral edge cases involving re-throwing, finally block return values, and the difference between assertions and exceptions. These patterns appear regularly in tricky code snippets.
// Re-throwing the same exception after logging it
try {
    process();
} catch (Exception e) {
    logger.error("Error processing", e);
    throw e;  // Re-throws the original exception unchanged
}

// Wrapping and re-throwing as a different type
// Use this to convert a low-level exception into a domain-level one
try {
    process();
} catch (SQLException e) {
    throw new RuntimeException("Processing failed", e);  // Cause is preserved
}

// finally block with return - a common exam trap
public int getValue() {
    try {
        return 1;   // Execution starts to return 1...
    } finally {
        return 2;   // ...but finally runs and returns 2 instead
    }
    // The method always returns 2, regardless of the try block return
}

// Similarly, a return in finally suppresses any exception from try or catch:
public int riskyMethod() {
    try {
        throw new RuntimeException("Error");
    } finally {
        return 42;  // Exception is silently discarded - method returns 42
    }
}

// Assertions versus exceptions
// Use assert for:
//   - Verifying internal logic invariants during development
//   - Conditions that should never occur if the code is correct
//   - Note: assertions can be disabled at runtime with the -da JVM flag

// Use exceptions for:
//   - Validating arguments passed in by callers
//   - Handling external conditions such as missing files or network failures
//   - Business logic errors that calling code must respond to
Exam Tip: A return statement inside a finally block suppresses both any return value from the try or catch block AND any exception that was being propagated. This is considered a code smell in real projects but is a favorite exam trick. Always trace through finally blocks carefully when you see return statements inside them.

1Z0-830 Java SE 21 Certification - Table of Contents

Master all exam topics with comprehensive study guides and practice examples.


Popular Posts