8.2 try-catch and finally Blocks
The most fundamental syntax for implementing exception handling directly is the try-catch block. Instead of crashing immediately when an error occurs, Java provides this construct as a safety net that lets you gracefully sidestep or resolve the problem.
1. Basic Structure of try-catch
A try-catch block is divided into a try block where potentially failing code is placed, and a catch block that handles the exception if one occurs.
try {
// Code that might throw an exception
} catch (Exception1 e1) {
// Code to run when Exception1 occurs
} catch (Exception2 e2) {
// Code to run when Exception2 occurs
}
tryblock: The monitored zone. Java watches whether the code executes normally.catchblock: When an exception occurs (throw) insidetry, Java immediately stopstryexecution and runs the code inside the matchingcatchblock. The reference variable (e.g.,e1,e2) receives the exception object containing detailed error information.
Example: Division by Zero (ArithmeticException)
public class TryCatchBasic {
public static void main(String[] args) {
System.out.println("Program starting.");
int num = 100;
int result = 0;
try {
// Error occurs here — attempting to divide by zero!
result = num / 0;
// Code after the error is never reached; flow jumps to catch
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Exception! Cannot divide by zero.");
System.out.println("Error message: " + e.getMessage());
}
// Program reaches here normally because catch prevented abnormal termination
System.out.println("Program ends normally.");
}
}
Output:
Program starting.
Exception! Cannot divide by zero.
Error message: / by zero
Program ends normally.
2. Multiple catch Blocks
When multiple types of exceptions can occur inside a try block, you can chain multiple catch blocks.
public class MultiCatchExample {
public static void main(String[] args) {
String[] data = {"10", null, "abc", "20"};
for (String item : data) {
try {
// Two types of exceptions can occur
int value = Integer.parseInt(item); // NumberFormatException possible
int result = 100 / value; // ArithmeticException possible
System.out.println("100 / " + value + " = " + result);
} catch (NullPointerException e) {
System.out.println("Null value received.");
} catch (NumberFormatException e) {
System.out.println("Not a number: " + item);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero.");
} catch (Exception e) {
// Handles all remaining exceptions not caught above
System.out.println("Unknown error: " + e.getMessage());
}
}
}
}
Output:
100 / 10 = 10
Null value received.
Not a number: abc
100 / 20 = 5
Multiple catch blocks are evaluated top to bottom. Parent exception classes must go below child ones. If the ancestor Exception is at the top, all catch blocks below it will never execute — this causes a compile error.
3. Multi-catch: Java 7+ Feature
Since Java 7, multiple exceptions can be joined with | and handled in a single catch block.
import java.io.IOException;
import java.sql.SQLException;
public class MultiCatchJava7 {
public static void main(String[] args) {
try {
riskyOperation();
} catch (IOException | SQLException e) {
// Handles either IOException or SQLException
System.out.println("IO or DB error: " + e.getMessage());
// Note: in multi-catch, variable e is effectively final
} catch (Exception e) {
System.out.println("Other error: " + e.getMessage());
}
}
static void riskyOperation() throws IOException, SQLException {
// Throw an exception for demonstration
throw new IOException("File error occurred");
}
}
catch (ParentException | ChildException e) is a compile error. If two exceptions have an inheritance relationship, you only need the parent. For example catch (IOException | Exception e) is redundant since IOException is already a subclass of Exception.
4. finally Block: Code That Always Runs
When there is cleanup code that must always run regardless of whether an exception occurred or everything ran normally, use the finally block. It is used when closing database connections, file streams, network sockets after use.
public class FinallyExample {
static String databaseConnection = null;
public static void main(String[] args) {
System.out.println("=== Normal Execution Case ===");
processData(10);
System.out.println("\n=== Exception Case ===");
processData(0);
}
static void processData(int divisor) {
try {
databaseConnection = "DB connected";
System.out.println(databaseConnection);
int result = 100 / divisor; // throws exception when divisor is 0
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Calculation error: " + e.getMessage());
} finally {
// Always executes regardless of exceptions
// Even if there is a return statement in try or catch, finally still runs!
databaseConnection = null;
System.out.println("DB connection safely closed.");
}
}
}
Output:
=== Normal Execution Case ===
DB connected
Result: 10
DB connection safely closed.
=== Exception Case ===
DB connected
Calculation error: / by zero
DB connection safely closed.
finally and return
public class FinallyWithReturn {
public static int testFinally() {
try {
System.out.println("try block executes");
return 1; // even with return...
} finally {
System.out.println("finally block always runs");
// Caution: a return inside finally overwrites the try's return
}
}
public static void main(String[] args) {
int result = testFinally();
System.out.println("Result: " + result); // 1
}
}
5. Exception Propagation
When an exception is not handled in the method where it occurs, it propagates up the call stack to the caller.
public class ExceptionPropagation {
// Deepest method: does not handle, throws upward
static void methodC() {
int result = 10 / 0; // ArithmeticException
}
// Middle method: does not handle, propagates upward
static void methodB() {
methodC(); // methodC's exception propagates here
}
// Top-level method: finally handles it
static void methodA() {
try {
methodB(); // methodB's exception propagates here
} catch (ArithmeticException e) {
System.out.println("methodA handles exception: " + e.getMessage());
}
}
public static void main(String[] args) {
methodA();
System.out.println("Normal termination");
}
}
Output:
methodA handles exception: / by zero
Normal termination
6. throws Keyword: Delegating Exception Handling
Using throws in a method declaration passes the responsibility of handling the exception to the caller.
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowsExample {
// Delegate IOException handling to the caller with throws
static String readFileContent(String filePath) throws IOException {
FileInputStream fis = new FileInputStream(filePath);
// file reading logic (omitted)
fis.close();
return "file content";
}
// Multiple exceptions can be delegated
static void complexOperation(String path, String numStr)
throws IOException, NumberFormatException {
String content = readFileContent(path);
int value = Integer.parseInt(numStr);
System.out.println("Processing complete: " + content + ", " + value);
}
public static void main(String[] args) {
// The caller (main) must handle the exception
try {
readFileContent("config.txt");
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
}
}
}
7. Wrapping Checked Exceptions as Unchecked
In practice, a common pattern is wrapping a Checked Exception inside a RuntimeException and re-throwing it.
import java.io.IOException;
public class WrapExceptionPattern {
// Wrap Checked Exception as Unchecked and rethrow
static void loadConfig(String path) {
try {
readConfigFile(path);
} catch (IOException e) {
// Wrap IOException inside RuntimeException and rethrow
throw new RuntimeException("Failed to load config file: " + path, e);
}
}
static void readConfigFile(String path) throws IOException {
throw new IOException("File not found: " + path);
}
public static void main(String[] args) {
try {
loadConfig("app.properties");
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
System.out.println("Cause: " + e.getCause().getMessage());
}
}
}
8. Exception Information Methods Reference
| Method | Return Type | Description |
|---|---|---|
getMessage() | String | Returns the exception message |
toString() | String | Exception class name + message |
printStackTrace() | void | Prints the full exception call stack |
getClass().getName() | String | Exception class fully-qualified name |
getCause() | Throwable | The cause exception that triggered this one |
public class ExceptionMethodsDemo {
public static void main(String[] args) {
try {
String s = null;
s.length(); // NullPointerException
} catch (NullPointerException e) {
System.out.println("getMessage(): " + e.getMessage());
System.out.println("toString(): " + e.toString());
System.out.println("getClass(): " + e.getClass().getName());
}
try {
try {
Integer.parseInt("abc");
} catch (NumberFormatException cause) {
throw new RuntimeException("Data processing failed", cause);
}
} catch (RuntimeException e) {
System.out.println("Message: " + e.getMessage());
System.out.println("Cause: " + e.getCause().getMessage());
}
}
}
9. Practical Example: File Reading Simulation
public class FileReadSimulation {
// Simulates reading a file
static String readFile(String filename) throws Exception {
if (filename == null) {
throw new NullPointerException("Filename is null");
}
if (filename.isEmpty()) {
throw new IllegalArgumentException("Filename is empty");
}
if (!filename.endsWith(".txt")) {
throw new UnsupportedOperationException("Only .txt files supported: " + filename);
}
if (filename.equals("missing.txt")) {
throw new Exception("File not found: " + filename);
}
return "File content: Hello, Java!";
}
public static void main(String[] args) {
String[] testFiles = {"data.txt", null, "", "image.png", "missing.txt"};
for (String file : testFiles) {
System.out.println("--- Attempting to read: " + file + " ---");
try {
String content = readFile(file);
System.out.println("Success: " + content);
} catch (NullPointerException e) {
System.out.println("Error: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.out.println("Input error: " + e.getMessage());
} catch (UnsupportedOperationException e) {
System.out.println("Unsupported file type: " + e.getMessage());
} catch (Exception e) {
System.out.println("File error: " + e.getMessage());
} finally {
System.out.println("File processing complete (success or failure)\n");
}
}
}
}
Output:
--- Attempting to read: data.txt ---
Success: File content: Hello, Java!
File processing complete (success or failure)
--- Attempting to read: null ---
Error: Filename is null
File processing complete (success or failure)
--- Attempting to read: ---
Input error: Filename is empty
File processing complete (success or failure)
--- Attempting to read: image.png ---
Unsupported file type: Only .txt files supported: image.png
File processing complete (success or failure)
--- Attempting to read: missing.txt ---
File error: File not found: missing.txt
File processing complete (success or failure)
Since Java 7, resources implementing AutoCloseable (files, DB connections, etc.) declared inside try (...) are automatically closed when the block exits. This is much safer and more concise than manually closing in finally. See the next chapter try-with-resources for details.
In the next chapter we will learn about Custom Exceptions specialized for business logic.