Checked Exceptions
Checked Exceptions
Checked exceptions are exceptions that the compiler forces you to either catch or declare in the method signature using the throws keyword. They represent recoverable conditions that are outside the normal flow of the program but can reasonably be anticipated and handled.
Checked exceptions are subclasses of Exception but NOT subclasses of RuntimeException. The compiler checks for them at compile time, hence the name.
Common checked exceptions:
IOException— I/O operations (file not found, network errors)FileNotFoundException— specific file does not existSQLException— database access errorsClassNotFoundException— class not found at runtimeInterruptedException— a thread was interruptedParseException— parsing error (e.g., date parsing)
import java.io.*;
import java.sql.*;
import java.util.Date;
import java.text.*;
public class CheckedExceptionsDemo {
// Must declare checked exceptions with throws
public static String readConfig(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
String config = br.readLine();
br.close();
return config;
}
// Must catch or declare SQLException
public static void connectToDatabase() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
// Must catch ParseException
public static Date parseDate(String dateStr) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(dateStr);
}
// Or catch them locally
public static void handleLocally() {
try {
String config = readConfig("app.properties");
System.out.println("Config: " + config);
} catch (IOException e) {
System.out.println("Using default config");
}
}
// Multiple checked exceptions
public static void complexOperation() {
try {
connectToDatabase();
Date date = parseDate("2024-01-15");
System.out.println("Parsed: " + date);
} catch (SQLException e) {
System.out.println("Database error: " + e.getMessage());
} catch (ParseException e) {
System.out.println("Parse error: " + e.getMessage());
}
}
public static void main(String[] args) {
handleLocally();
complexOperation();
}
}
The compiler-enforced handling of checked exceptions is a controversial feature. Supporters argue it makes code more robust by forcing error handling. Critics argue it leads to cluttered code with empty catch blocks. The best practice is to catch checked exceptions only when you can meaningfully recover from them; otherwise, let them propagate.
Unchecked Exceptions
Unchecked Exceptions
Unchecked exceptions are subclasses of RuntimeException. The compiler does not force you to catch or declare them. They typically indicate programming bugs and should be fixed at the source rather than caught generically.
Common unchecked exceptions:
NullPointerException— accessing methods/fields on nullArrayIndexOutOfBoundsException— invalid array indexStringIndexOutOfBoundsException— invalid string indexIllegalArgumentException— invalid method argumentIllegalStateException— method called at wrong timeArithmeticException— arithmetic errors (division by zero)NumberFormatException— invalid number formatClassCastException— invalid type castUnsupportedOperationException— unsupported operation
public class UncheckedExceptionsDemo {
// NullPointerException
public static void demonstrateNPE() {
String str = null;
// str.length(); // throws NullPointerException
try {
str.length();
} catch (NullPointerException e) {
System.out.println("NPE: " + e.getMessage());
}
}
// ArrayIndexOutOfBoundsException
public static void demonstrateAIOOBE() {
int[] arr = {1, 2, 3};
try {
int val = arr[5]; // Index 5 out of bounds
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid index: " + e.getMessage());
}
}
// IllegalArgumentException - commonly thrown by developers
public static void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
System.out.println("Age set to: " + age);
}
// ArithmeticException
public static void divide(int a, int b) {
try {
int result = a / b;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
}
}
// NumberFormatException
public static void parseNumber(String input) {
try {
int num = Integer.parseInt(input);
System.out.println("Number: " + num);
} catch (NumberFormatException e) {
System.out.println("Invalid number: " + input);
}
}
// ClassCastException
public static void demonstrateCCE() {
Object obj = "Hello";
try {
Integer num = (Integer) obj; // ClassCastException
} catch (ClassCastException e) {
System.out.println("Invalid cast: " + e.getMessage());
}
}
public static void main(String[] args) {
demonstrateNPE();
demonstrateAIOOBE();
setAge(25);
divide(10, 0);
parseNumber("abc");
demonstrateCCE();
}
}
Best practices for unchecked exceptions:
- Do not catch them generically unless absolutely necessary
- Use
Objects.requireNonNull()for null checks - Validate method arguments and throw
IllegalArgumentExceptionwith descriptive messages - Fix the root cause rather than catching the symptom
Errors
Errors
Errors are subclasses of java.lang.Error and represent serious system-level problems that applications should not attempt to catch. They are thrown by the JVM and indicate conditions that are typically unrecoverable.
Common errors:
OutOfMemoryError— JVM ran out of memoryStackOverflowError— infinite recursionVirtualMachineError— JVM internal errorAssertionError— assertion failureNoClassDefFoundError— class definition not found at runtimeThreadDeath— thread killed by JVM
public class ErrorsDemo {
// StackOverflowError from infinite recursion
public static void infiniteRecursion() {
infiniteRecursion(); // calls itself forever
}
// OutOfMemoryError from creating too many objects
public static void outOfMemory() {
// Allocate memory until JVM runs out
// List<byte[]> list = new ArrayList<>();
// while (true) {
// list.add(new byte[1024 * 1024]); // 1MB each
// }
}
// Why you should NOT catch Errors
public static void shouldNotCatch() {
try {
infiniteRecursion();
} catch (StackOverflowError e) {
// Generally should NOT do this
// Errors indicate unrecoverable problems
System.out.println("Stack overflow: " + e.getMessage());
}
}
// Exception vs Error comparison
public static void demonstrateDifference() {
// Exception - can recover
try {
String str = null;
str.length(); // RuntimeException
} catch (NullPointerException e) {
System.out.println("Recovered from NPE");
}
// Error - should not recover
// try {
// infiniteRecursion(); // StackOverflowError
// } catch (StackOverflowError e) {
// // Don't catch errors unless you're sure you can recover
// }
}
public static void main(String[] args) {
// demonstrateDifference();
// infiniteRecursion(); // Don't actually run this
System.out.println("Errors should generally not be caught");
}
}
When you might catch an Error:
- In a server application to prevent one request from crashing the entire server
- In a test framework to verify error conditions
- When implementing a fallback mechanism for specific known errors
When to NOT catch an Error:
- In normal application code
- When the application state is corrupted
- When you cannot meaningfully recover
The key distinction: Exceptions are for conditions you can recover from. Errors are for conditions that indicate the JVM is in a bad state.
Handling Strategy
Handling Strategy: Catch vs Declare
Choosing whether to catch an exception or let it propagate is one of the most important design decisions in exception handling. The right strategy depends on your role in the call stack and whether you can meaningfully recover.
When to catch:
- You can recover and continue execution
- You need to provide fallback behavior
- You need to clean up resources before propagating
- You want to add context before rethrowing
When to declare:
- You cannot recover from the exception
- The caller is in a better position to handle it
- You want to let the exception propagate up the call stack
- The method is a utility that should not make policy decisions
import java.io.*;
import java.util.*;
public class HandlingStrategyDemo {
// BAD: Swallowing exceptions silently
public static void badReadFile(String path) {
try {
BufferedReader br = new BufferedReader(new FileReader(path));
System.out.println(br.readLine());
br.close();
} catch (IOException e) {
// Silent failure - BAD practice!
}
}
// GOOD: Catch with meaningful recovery
public static String goodReadFile(String path) {
try {
BufferedReader br = new BufferedReader(new FileReader(path));
String content = br.readLine();
br.close();
return content;
} catch (IOException e) {
// Log the error, provide fallback
System.err.println("Failed to read " + path + ": " + e.getMessage());
return "default value";
}
}
// GOOD: Declare when you can't recover
public static List<String> loadAllFiles(List<String> paths) throws IOException {
List<String> results = new ArrayList<>();
for (String path : paths) {
results.add(readFileOrThrow(path)); // Let caller handle
}
return results;
}
private static String readFileOrThrow(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
String content = br.readLine();
br.close();
return content;
}
// GOOD: Catch, add context, and rethrow
public static void processData(String path) {
try {
String data = readFileOrThrow(path);
int value = Integer.parseInt(data.trim());
System.out.println("Processed: " + (value * 2));
} catch (IOException e) {
throw new RuntimeException("Failed to load data from " + path, e);
} catch (NumberFormatException e) {
throw new RuntimeException("Invalid data format in " + path, e);
}
}
// GOOD: Catch for cleanup, then rethrow
public static void processWithCleanup(String path) {
InputStream is = null;
try {
is = new FileInputStream(path);
// process...
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
throw new RuntimeException("Processing failed", e);
} finally {
if (is != null) {
try { is.close(); } catch (IOException e) { /* ignore */ }
}
}
}
// Decision framework
public static String decideHandling(int errorCode) {
try {
return processErrorCode(errorCode);
} catch (IllegalArgumentException e) {
// We can recover: return a default
return "unknown";
} catch (IOException e) {
// We cannot recover: propagate
throw new RuntimeException("Cannot process error code", e);
}
}
private static String processErrorCode(int code) throws IOException {
if (code < 0) throw new IllegalArgumentException("Negative code");
if (code > 500) throw new IOException("Server error");
return "OK";
}
public static void main(String[] args) {
System.out.println(goodReadFile("config.txt"));
try {
List<String> files = loadAllFiles(Arrays.asList("a.txt", "b.txt"));
} catch (IOException e) {
System.out.println("Could not load files: " + e.getMessage());
}
}
}
The golden rule: Catch when you can recover. Declare when you cannot. Never swallow exceptions silently.
Practice Problems
Write a method `safeGet(int[] arr, int index)` that returns the element at the given index, or -1 if the index is out of bounds. Handle the unchecked exception.
Solution
public class SafeArrayAccess {
public static int safeGet(int[] arr, int index) {
try {
return arr[index];
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
}
}Write a method `identifyType(Object obj)` that throws `NullPointerException` if obj is null, `IllegalArgumentException` if obj is not a String or Integer, and returns a String description of the object's type otherwise.
Solution
public class ExceptionTypeIdentifier {
public static String identifyType(Object obj) {
if (obj == null) {
throw new NullPointerException("Object cannot be null");
}
if (obj instanceof String) {
return "String: " + obj;
} else if (obj instanceof Integer) {
return "Integer: " + obj;
} else {
throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getSimpleName());
}
}
}Quiz
1. Which of these is a checked exception?
2. What is the main difference between checked and unchecked exceptions?
3. When should you catch an Error in Java?
4. What is the primary purpose of Checked vs Unchecked Exceptions?
Flashcards
Question
What is a checked exception?
Click to reveal answer
Answer
A checked exception is a subclass of Exception (not RuntimeException) that the compiler forces you to either catch or declare in the method signature using throws. Example: IOException, SQLException.
Question
Name 3 common unchecked exceptions in Java
Click to reveal answer
Answer
NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException. All are subclasses of RuntimeException and do not need to be declared or caught.
Question
When should you catch an exception vs declare it?
Click to reveal answer
Answer
Catch when you can recover from the error and provide fallback behavior. Declare when you cannot recover and the caller is in a better position to handle it. Never swallow exceptions silently.
Question
What is Checked vs Unchecked Exceptions?
Click to reveal answer
Answer
Checked vs Unchecked Exceptions is a key concept in Java programming.
Question
When to use Checked vs Unchecked Exceptions?
Click to reveal answer
Answer
Use Checked vs Unchecked Exceptions when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.Checked exceptions must be caught or declared; unchecked exceptions do not
- 2.Errors indicate serious JVM problems and should generally not be caught
- 3.Never swallow exceptions silently — at minimum, log the error
- 4.Catch when you can recover; declare when you cannot
- 5.Unchecked exceptions indicate programming bugs that should be fixed
Interview Tips
- •Know the difference between checked and unchecked exceptions and give concrete examples of each
- •Explain why Java has checked exceptions and discuss the debate around them
- •Describe when you would catch an Error vs when you would let it propagate
- •Discuss best practices: never swallow exceptions, catch specific types, add context when rethrowing
Cheat Sheet
Checked vs Unchecked Exceptions
Checked (compile-time enforced)
- Subclass of Exception (not RuntimeException)
- Must catch or declare with throws
- Examples: IOException, SQLException, ParseException
- Represents recoverable conditions
Unchecked (runtime)
- Subclass of RuntimeException
- No compiler enforcement
- Examples: NullPointerException, IllegalArgumentException, ArithmeticException
- Represents programming bugs
Errors
- Subclass of Error
- JVM/system level problems
- Examples: OutOfMemoryError, StackOverflowError
- Generally should NOT catch
Decision Rule
Catch = can recover, add fallback
Declare = cannot recover, let caller handle