Java Exception Handling: Best Practices
🎯 Exception là gì?
Exception là một event xảy ra trong quá trình thực thi chương trình làm gián đoạn flow bình thường.
int result = 10 / 0; // ArithmeticException!
1. Hierarchy of Exceptions
Throwable
├── Error (Unchecked)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── RuntimeException (Unchecked)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ └── IllegalArgumentException
└── Checked Exceptions
├── IOException
├── SQLException
└── ClassNotFoundException
Checked vs Unchecked
Checked Exceptions:
- Compiler bắt buộc phải handle
- Ví dụ: IOException, SQLException
// Phải có try-catch hoặc throws
public void readFile() throws IOException {
FileReader fr = new FileReader("file.txt");
}
Unchecked Exceptions:
- RuntimeException và subclasses
- Không bắt buộc handle
// Không bắt buộc try-catch
public void divide(int a, int b) {
return a / b; // Có thể throw ArithmeticException
}
2. Try-Catch Basics
Cơ bản
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!");
}
Multiple Catch Blocks
try {
String text = null;
text.length(); // NullPointerException
int num = Integer.parseInt("abc"); // NumberFormatException
} catch (NullPointerException e) {
System.out.println("Null value!");
} catch (NumberFormatException e) {
System.out.println("Invalid number format!");
}
Multi-catch (Java 7+)
try {
// some code
} catch (IOException | SQLException e) {
System.out.println("Database or File error: " + e.getMessage());
}
Finally Block
FileReader fr = null;
try {
fr = new FileReader("file.txt");
// read file
} catch (IOException e) {
e.printStackTrace();
} finally {
// ALWAYS execute
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Try-with-resources (Java 7+)
// Auto-close resources
try (FileReader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr)) {
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
// fr và br tự động close!
3. Custom Exceptions
Tạo Exception riêng
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
super("Insufficient funds: " + amount);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
Sử dụng
public class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}
}
// Client code
try {
account.withdraw(1000);
} catch (InsufficientFundsException e) {
System.out.println("Need more: " + e.getAmount());
}
4. Best Practices
✅ DO: Catch specific exceptions
// GOOD
try {
// code
} catch (FileNotFoundException e) {
// Handle file not found
} catch (IOException e) {
// Handle other IO errors
}
// BAD - Quá general
try {
// code
} catch (Exception e) { // Catch all!
// What happened???
}
✅ DO: Provide context in exceptions
// GOOD
throw new IllegalArgumentException(
"User ID must be positive, got: " + userId
);
// BAD - No context
throw new IllegalArgumentException("Invalid input");
✅ DO: Log exceptions properly
// GOOD
try {
// code
} catch (SQLException e) {
logger.error("Failed to query user with ID: " + userId, e);
throw new UserNotFoundException("User not found: " + userId);
}
// BAD - Swallow exception
try {
// code
} catch (Exception e) {
// Do nothing
}
❌ DON'T: Use exceptions for control flow
// BAD
try {
int i = 0;
while (true) {
array[i++];
}
} catch (ArrayIndexOutOfBoundsException e) {
// End of array
}
// GOOD
for (int i = 0; i < array.length; i++) {
// process array[i]
}
❌ DON'T: Catch Throwable or Error
// BAD - Don't catch Error!
try {
// code
} catch (Throwable t) {
// This catches OutOfMemoryError, StackOverflowError, etc.
}
// GOOD
try {
// code
} catch (Exception e) {
// Handle application exceptions only
}
5. Exception Handling Patterns
Pattern 1: Fail Fast
public void processUser(User user) {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
if (user.getId() < 0) {
throw new IllegalArgumentException("User ID must be positive");
}
// Process user
}
Pattern 2: Wrap and Rethrow
public User getUser(int id) {
try {
return database.queryUser(id);
} catch (SQLException e) {
throw new UserNotFoundException("User not found: " + id, e);
}
}
Pattern 3: Retry Mechanism
public void fetchDataWithRetry(String url) {
int maxRetries = 3;
int attempt = 0;
while (attempt < maxRetries) {
try {
fetchData(url);
return; // Success!
} catch (NetworkException e) {
attempt++;
if (attempt >= maxRetries) {
throw e; // Give up
}
Thread.sleep(1000 * attempt); // Exponential backoff
}
}
}
6. Kết luận
Key Takeaways:
- Catch specific exceptions, không catch-all
- Always provide context trong error messages
- Đừng dùng exceptions cho control flow
- Log exceptions trước khi rethrow
- Use try-with-resources cho auto-closing resources
Remember: Good exception handling = Better debugging experience!
