Exceptions
Try-Catch
Handling Exceptions
Your program reads a file that might not exist. Without exception handling, it crashes. With try-catch, you can detect the error and respond gracefully - show a message, try a different file, or use default data.
Basic try-catch
Catch and handle an exception.
// Basic Try-Catch: Division by Zero
public class BasicTryCatch {
public static void main(String[] args) {
System.out.println("=== Basic Try-Catch ===\n");
// Without try-catch - would crash
// int result = 10 / 0; // ArithmeticException!
// With try-catch - handle gracefully
int numerator = 10;
int denominator = ;
try {
System.out.println("Attempting division...");
int result = numerator / denominator;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Error caught!");
System.out.println("Exception type: " + e.getClass().getName());
System.out.println("Message: " + e.getMessage());
}
System.out.println("\nProgram continues normally...");
// Demonstrate successful division
System.out.println("\n--- Successful Division ---");
int a = 20;
int b = 4;
try {
int result = a / b;
System.out.println(a + " / " + b + " = " + result);
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
}
// Multiple attempts in one try block
System.out.println("\n--- Multiple Operations ---");
int[] numbers = {10, 5, 0, 2};
int dividend = 100;
for (int num : numbers) {
try {
int result = dividend / num;
System.out.println(dividend + " / " + num + " = " + result);
} catch (ArithmeticException e) {
System.out.println(dividend + " / " + num + " = Error (division by zero)");
}
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. try block contains risky code
2. catch block handles the exception
3. Specify exception type to catch
4. e.getMessage() gives error details
5. Program continues after catch block
""");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// Basic Try-Catch: Division by Zero
public class BasicTryCatch {
public static void main(String[] args) {
System.out.println("=== Basic Try-Catch ===\n");
// Without try-catch - would crash
// int result = 10 / 0; // ArithmeticException!
// With try-catch - handle gracefully
int numerator = 10;
int denominator = ;
try {
System.out.println("Attempting division...");
int result = numerator / denominator;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Error caught!");
System.out.println("Exception type: " + e.getClass().getName());
System.out.println("Message: " + e.getMessage());
}
System.out.println("\nProgram continues normally...");
// Demonstrate successful division
System.out.println("\n--- Successful Division ---");
int a = 20;
int b = 4;
try {
int result = a / b;
System.out.println(a + " / " + b + " = " + result);
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
}
// Multiple attempts in one try block
System.out.println("\n--- Multiple Operations ---");
int[] numbers = {10, 5, 0, 2};
int dividend = 100;
for (int num : numbers) {
try {
int result = dividend / num;
System.out.println(dividend + " / " + num + " = " + result);
} catch (ArithmeticException e) {
System.out.println(dividend + " / " + num + " = Error (division by zero)");
}
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. try block contains risky code
2. catch block handles the exception
3. Specify exception type to catch
4. e.getMessage() gives error details
5. Program continues after catch block
""");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Code that might fail goes in try. Error handling goes in catch.
Catch array bounds exception
Handle accessing invalid array indices.
// Array Index Out of Bounds Exception
public class ArrayBounds {
public static void main(String[] args) {
System.out.println("=== Array Bounds Exception ===\n");
// Create an array
String[] fruits = {"Apple", "Banana", "Cherry"};
System.out.println("Array: [Apple, Banana, Cherry]");
System.out.println("Valid indices: 0, 1, 2");
System.out.println("Length: " + fruits.length);
// Access valid index
System.out.println("\n--- Valid Access ---");
try {
String fruit = fruits[1];
System.out.println("fruits[1] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
}
// Access invalid index (too high)
System.out.println("\n--- Invalid Access (index too high) ---");
try {
String fruit = fruits[5];
System.out.println("fruits[5] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: Index 5 out of bounds");
System.out.println("Message: " + e.getMessage());
}
// Access invalid index (negative)
System.out.println("\n--- Invalid Access (negative index) ---");
try {
String fruit = fruits[-1];
System.out.println("fruits[-1] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: Negative index");
System.out.println("Message: " + e.getMessage());
}
// Safe access pattern
System.out.println("\n--- Safe Access Pattern ---");
int[] indicesToTry = {0, 2, 5, -1, 1};
for (int index : indicesToTry) {
try {
String fruit = fruits[index];
System.out.println("fruits[" + index + "] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("fruits[" + index + "] = Invalid index!");
}
}
// Alternative: Check before accessing
System.out.println("\n--- Check Before Access ---");
int requestedIndex = ;
if (requestedIndex >= 0 && requestedIndex < fruits.length) {
System.out.println("fruits[" + requestedIndex + "] = " + fruits[requestedIndex]);
} else {
System.out.println("Index " + requestedIndex + " is out of bounds (0-" + (fruits.length - 1) + ")");
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Arrays have fixed size (0 to length-1)
2. Invalid index throws ArrayIndexOutOfBoundsException
3. Both too high AND negative indices are invalid
4. Can catch and handle, or check bounds first
5. getMessage() shows the invalid index number
""");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// Array Index Out of Bounds Exception
public class ArrayBounds {
public static void main(String[] args) {
System.out.println("=== Array Bounds Exception ===\n");
// Create an array
String[] fruits = {"Apple", "Banana", "Cherry"};
System.out.println("Array: [Apple, Banana, Cherry]");
System.out.println("Valid indices: 0, 1, 2");
System.out.println("Length: " + fruits.length);
// Access valid index
System.out.println("\n--- Valid Access ---");
try {
String fruit = fruits[1];
System.out.println("fruits[1] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
}
// Access invalid index (too high)
System.out.println("\n--- Invalid Access (index too high) ---");
try {
String fruit = fruits[5];
System.out.println("fruits[5] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: Index 5 out of bounds");
System.out.println("Message: " + e.getMessage());
}
// Access invalid index (negative)
System.out.println("\n--- Invalid Access (negative index) ---");
try {
String fruit = fruits[-1];
System.out.println("fruits[-1] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: Negative index");
System.out.println("Message: " + e.getMessage());
}
// Safe access pattern
System.out.println("\n--- Safe Access Pattern ---");
int[] indicesToTry = {0, 2, 5, -1, 1};
for (int index : indicesToTry) {
try {
String fruit = fruits[index];
System.out.println("fruits[" + index + "] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("fruits[" + index + "] = Invalid index!");
}
}
// Alternative: Check before accessing
System.out.println("\n--- Check Before Access ---");
int requestedIndex = ;
if (requestedIndex >= 0 && requestedIndex < fruits.length) {
System.out.println("fruits[" + requestedIndex + "] = " + fruits[requestedIndex]);
} else {
System.out.println("Index " + requestedIndex + " is out of bounds (0-" + (fruits.length - 1) + ")");
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Arrays have fixed size (0 to length-1)
2. Invalid index throws ArrayIndexOutOfBoundsException
3. Both too high AND negative indices are invalid
4. Can catch and handle, or check bounds first
5. getMessage() shows the invalid index number
""");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// Array Index Out of Bounds Exception
public class ArrayBounds {
public static void main(String[] args) {
System.out.println("=== Array Bounds Exception ===\n");
// Create an array
String[] fruits = {"Apple", "Banana", "Cherry"};
System.out.println("Array: [Apple, Banana, Cherry]");
System.out.println("Valid indices: 0, 1, 2");
System.out.println("Length: " + fruits.length);
// Access valid index
System.out.println("\n--- Valid Access ---");
try {
String fruit = fruits[1];
System.out.println("fruits[1] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
}
// Access invalid index (too high)
System.out.println("\n--- Invalid Access (index too high) ---");
try {
String fruit = fruits[5];
System.out.println("fruits[5] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: Index 5 out of bounds");
System.out.println("Message: " + e.getMessage());
}
// Access invalid index (negative)
System.out.println("\n--- Invalid Access (negative index) ---");
try {
String fruit = fruits[-1];
System.out.println("fruits[-1] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: Negative index");
System.out.println("Message: " + e.getMessage());
}
// Safe access pattern
System.out.println("\n--- Safe Access Pattern ---");
int[] indicesToTry = {0, 2, 5, -1, 1};
for (int index : indicesToTry) {
try {
String fruit = fruits[index];
System.out.println("fruits[" + index + "] = " + fruit);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("fruits[" + index + "] = Invalid index!");
}
}
// Alternative: Check before accessing
System.out.println("\n--- Check Before Access ---");
int requestedIndex = ;
if (requestedIndex >= 0 && requestedIndex < fruits.length) {
System.out.println("fruits[" + requestedIndex + "] = " + fruits[requestedIndex]);
} else {
System.out.println("Index " + requestedIndex + " is out of bounds (0-" + (fruits.length - 1) + ")");
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Arrays have fixed size (0 to length-1)
2. Invalid index throws ArrayIndexOutOfBoundsException
3. Both too high AND negative indices are invalid
4. Can catch and handle, or check bounds first
5. getMessage() shows the invalid index number
""");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
ArrayIndexOutOfBoundsException when accessing index outside array range.
Multiple catch blocks
Handle different exceptions differently.
// Multiple Catch Blocks
public class MultipleCatch {
public static void main(String[] args) {
System.out.println("=== Multiple Catch Blocks ===\n");
// Different exceptions need different handling
String[] data = {"10", "abc", "20", null, "30"};
int[] divisors = {2, 5, 0, 4, 1};
System.out.println("Processing data with multiple exception types...\n");
for (int i = 0; i < data.length; i++) {
System.out.println("--- Processing index " + i + " ---");
try {
// Step 1: Parse string to int (may throw NumberFormatException)
String str = data[i];
System.out.println("String value: " + str);
int number = Integer.parseInt(str);
System.out.println("Parsed number: " + number);
// Step 2: Divide (may throw ArithmeticException)
int divisor = divisors[i];
int result = number / divisor;
System.out.println("Result: " + number + " / " + divisor + " = " + result);
} catch (NumberFormatException e) {
System.out.println("ERROR: Cannot parse '" + data[i] + "' as number");
System.out.println(" Type: NumberFormatException");
} catch (ArithmeticException e) {
System.out.println("ERROR: Division by zero");
System.out.println(" Type: ArithmeticException");
} catch (NullPointerException e) {
System.out.println("ERROR: Null value encountered");
System.out.println(" Type: NullPointerException");
}
System.out.println();
}
// Multi-catch syntax (Java 7+)
System.out.println("=== Multi-Catch Syntax ===\n");
String[] testValues = {"100", "bad", null};
int testDivisor = 10;
for (String val : testValues) {
try {
int num = Integer.parseInt(val);
int result = num / testDivisor;
System.out.println(val + " / " + testDivisor + " = " + result);
} catch (NumberFormatException | NullPointerException e) {
// Handle both the same way
System.out.println("Invalid input: " + val);
System.out.println(" Exception: " + e.getClass().getSimpleName());
}
}
// Order matters - specific before general
System.out.println("\n=== Catch Order (Specific First) ===\n");
demonstrateCatchOrder();
System.out.println("=== Key Points ===");
System.out.println("""
1. Multiple catch blocks handle different exceptions
2. Order: specific exceptions before general ones
3. Multi-catch: catch (TypeA | TypeB e) for same handling
4. Only ONE catch block executes per exception
5. Put most specific exception types first
""");
}
static void demonstrateCatchOrder() {
String value = "not a number";
try {
int num = Integer.parseInt(value);
System.out.println("Parsed: " + num);
} catch (NumberFormatException e) {
// Specific exception - caught first
System.out.println("Caught NumberFormatException (specific)");
} catch (IllegalArgumentException e) {
// Parent of NumberFormatException - never reached for NFE
System.out.println("Caught IllegalArgumentException (parent)");
} catch (Exception e) {
// Most general - catches anything else
System.out.println("Caught Exception (general)");
}
System.out.println("\nNote: NumberFormatException extends IllegalArgumentException");
System.out.println("So the specific catch must come first!");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Specific exceptions first, general exception last. Order matters.
Exception hierarchy
Catch parent type to handle all subtypes.
// Exception Hierarchy
public class ExceptionHierarchy {
public static void main(String[] args) {
System.out.println("=== Exception Hierarchy ===\n");
// Exception inheritance tree
System.out.println("Java Exception Hierarchy:");
System.out.println("""
Throwable
├── Error (serious, don't catch)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception (catch these)
├── RuntimeException (unchecked)
│ ├── NullPointerException
│ ├── ArithmeticException
│ ├── IndexOutOfBoundsException
│ │ └── ArrayIndexOutOfBoundsException
│ ├── IllegalArgumentException
│ │ └── NumberFormatException
│ └── ClassCastException
└── IOException (checked)
└── FileNotFoundException
""");
// Catching parent catches all children
System.out.println("--- Parent Exception Catches Children ---\n");
Object[] testCases = {
"divide_zero", // ArithmeticException
"null_pointer", // NullPointerException
"bad_index", // ArrayIndexOutOfBoundsException
"bad_parse" // NumberFormatException
};
for (Object testCase : testCases) {
System.out.println("Test: " + testCase);
try {
triggerException((String)testCase);
} catch (RuntimeException e) {
// RuntimeException catches ALL runtime exceptions
System.out.println(" Caught: " + e.getClass().getSimpleName());
System.out.println(" Message: " + e.getMessage());
}
System.out.println();
}
// Catching Exception catches everything
System.out.println("--- Catching Exception (Most General) ---\n");
try {
triggerException("null_pointer");
} catch (Exception e) {
System.out.println("Exception catches any exception type");
System.out.println("Actual type: " + e.getClass().getName());
}
// instanceof check for specific handling
System.out.println("\n--- instanceof for Specific Handling ---\n");
for (Object testCase : testCases) {
try {
triggerException((String)testCase);
} catch (Exception e) {
handleWithInstanceof(e);
}
}
// Why use specific exceptions?
System.out.println("\n=== Why Catch Specific Exceptions? ===");
System.out.println("""
1. Different errors need different recovery
2. Don't hide unexpected errors
3. Better error messages for users
4. Easier debugging
5. Code is self-documenting
""");
}
static void triggerException(String type) {
switch (type) {
case "divide_zero" -> {
int result = 10 / 0;
}
case "null_pointer" -> {
String s = null;
s.length();
}
case "bad_index" -> {
int[] arr = {1, 2, 3};
int val = arr[10];
}
case "bad_parse" -> {
int num = Integer.parseInt("abc");
}
}
}
static void handleWithInstanceof(Exception e) {
System.out.print("Handling: ");
if (e instanceof ArithmeticException) {
System.out.println("Math error - check your calculations");
} else if (e instanceof NullPointerException) {
System.out.println("Null error - check for null values");
} else if (e instanceof ArrayIndexOutOfBoundsException) {
System.out.println("Index error - check array bounds");
} else if (e instanceof NumberFormatException) {
System.out.println("Parse error - check input format");
} else {
System.out.println("Unknown error: " + e.getClass().getSimpleName());
}
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Catching Exception catches everything. Be specific when possible.
Finally block
Code that always runs, even after exception.
// Finally Block
public class FinallyBlock {
public static void main(String[] args) {
System.out.println("=== Finally Block ===\n");
// Finally always executes
System.out.println("--- Finally with Exception ---");
try {
System.out.println("1. In try block");
int result = 10 / 0;
System.out.println("2. This won't print");
} catch (ArithmeticException e) {
System.out.println("3. In catch block");
} finally {
System.out.println("4. In finally block (ALWAYS runs)");
}
System.out.println("5. After try-catch-finally");
// Finally without exception
System.out.println("\n--- Finally without Exception ---");
try {
System.out.println("1. In try block");
int result = 10 / 2;
System.out.println("2. Result: " + result);
} catch (ArithmeticException e) {
System.out.println("3. In catch block (SKIPPED)");
} finally {
System.out.println("4. In finally block (ALWAYS runs)");
}
System.out.println("5. After try-catch-finally");
// Finally for cleanup
System.out.println("\n--- Finally for Cleanup ---");
demonstrateCleanup(true);
System.out.println();
demonstrateCleanup(false);
// Finally with return
System.out.println("\n--- Finally with Return ---");
int result1 = calculateWithReturn(10, 2);
System.out.println("Result: " + result1);
int result2 = calculateWithReturn(10, 0);
System.out.println("Result: " + result2);
// Try-finally without catch
System.out.println("\n--- Try-Finally (no catch) ---");
try {
System.out.println("Performing operation...");
// Operations here
} finally {
System.out.println("Cleanup runs even without catch block");
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. finally ALWAYS executes
2. Executes after try OR after catch
3. Use for cleanup: close files, release resources
4. finally runs even if return in try/catch
5. Can have try-finally without catch
""");
}
static void demonstrateCleanup(boolean success) {
System.out.println("Starting operation (success=" + success + ")");
// Simulating resource acquisition
System.out.println(" [RESOURCE] Acquired");
try {
if (success) {
System.out.println(" [OPERATION] Success");
} else {
System.out.println(" [OPERATION] About to fail...");
throw new RuntimeException("Operation failed");
}
} catch (RuntimeException e) {
System.out.println(" [ERROR] " + e.getMessage());
} finally {
// Always release the resource
System.out.println(" [RESOURCE] Released (finally)");
}
}
static int calculateWithReturn(int a, int b) {
try {
int result = a / b;
System.out.println(" Calculation successful");
return result;
} catch (ArithmeticException e) {
System.out.println(" Calculation failed");
return -1;
} finally {
// This runs BEFORE the return!
System.out.println(" Finally block executed");
}
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
finally { cleanup } runs whether exception occurred or not.
Exercise: Practical.java
Build a robust file reader with complete exception handling