Home/Java/Custom Exceptions in Java

Custom Exceptions in Java

Learn how to create your own exception types for specific errors

💡 Think of custom exceptions like creating your own alarm system with custom messages! Instead of just saying 'ERROR!', you can say 'Low Battery!' or 'Door Open!' - making it crystal clear what went wrong!

🎨 What are Custom Exceptions?

Custom exceptions are your own exception classes that extend Exception or RuntimeException. They let you create meaningful, domain-specific error types that make your code more readable and maintainable.

SimpleCustomException.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Creating a simple custom exception
class InvalidAgeException extends Exception {
// Constructor with message
public InvalidAgeException(String message) {
super(message); // Pass message to parent Exception class
}
}
public class SimpleCustomException {
public static void main(String[] args) {
try {
validateAge(15);
} catch (InvalidAgeException e) {
System.out.println("Caught custom exception!");
System.out.println("Message: " + e.getMessage());
}
try {
validateAge(25);
System.out.println("Age is valid!");
} catch (InvalidAgeException e) {
System.out.println("Error: " + e.getMessage());
}
}
public static void validateAge(int age) throws InvalidAgeException {
if (age < 18) {
// Throwing our custom exception!
throw new InvalidAgeException(
"Age " + age + " is not valid. Must be 18 or older."
);
}
}
}
// Output:
// Caught custom exception!
// Message: Age 15 is not valid. Must be 18 or older.
// Age is valid!

🔍 💡 Why Use Custom Exceptions?

Custom exceptions provide several benefits:

1

Clarity

Specific exception names make errors self-documenting

2

Organization

Group related errors under custom exception types

3

Context

Include relevant data and custom messages

4

Handling

Catch and handle different error types separately

WhyCustomExceptions.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Without custom exception - unclear!
public void transfer(Account from, Account to, double amount) throws Exception {
if (from.getBalance() < amount) {
throw new Exception("Error"); // What error?
}
}
// With custom exception - crystal clear!
public void transfer(Account from, Account to, double amount)
throws InsufficientFundsException {
if (from.getBalance() < amount) {
throw new InsufficientFundsException(
"Cannot transfer $" + amount +
". Available balance: $" + from.getBalance()
);
}
}
// Benefits are obvious:
// 1. Exception name tells exactly what went wrong
// 2. Can catch specific exception type
// 3. Can include relevant data (balance, amount)
// 4. Self-documenting code

💰 Real-World Example: Banking System

BankingExceptions.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Custom exception for insufficient funds
class InsufficientFundsException extends Exception {
private double balance;
private double withdrawAmount;
public InsufficientFundsException(double balance, double withdrawAmount) {
super("Insufficient funds! Balance: $" + balance +
", Requested: $" + withdrawAmount);
this.balance = balance;
this.withdrawAmount = withdrawAmount;
}
public double getShortfall() {
return withdrawAmount - balance;
}
public double getBalance() {
return balance;
}
}
class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(balance, amount);
}
balance -= amount;
System.out.println("Withdrew $" + amount);
System.out.println("New balance: $" + balance);
}
public double getBalance() {
return balance;
}
}
public class BankingExceptions {
public static void main(String[] args) {
BankAccount account = new BankAccount("ACC123", 1000.0);
System.out.println("Initial balance: $" + account.getBalance());
// Valid withdrawal
try {
account.withdraw(200);
} catch (InsufficientFundsException e) {
System.out.println("Error: " + e.getMessage());
}
// Invalid withdrawal - not enough funds!
try {
account.withdraw(2000);
} catch (InsufficientFundsException e) {
System.out.println("\n❌ " + e.getMessage());
System.out.println("You need $" + e.getShortfall() + " more");
}
}
}
// Output:
// Initial balance: $1000.0
// Withdrew $200.0
// New balance: $800.0
//
// ❌ Insufficient funds! Balance: $800.0, Requested: $2000.0
// You need $1200.0 more

🔧 Custom Exception with Multiple Constructors

FlexibleException.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Well-designed custom exception with multiple constructors
class InvalidEmailException extends Exception {
private String email;
private String reason;
// Constructor 1: Just message
public InvalidEmailException(String message) {
super(message);
}
// Constructor 2: Message with cause
public InvalidEmailException(String message, Throwable cause) {
super(message, cause);
}
// Constructor 3: With additional details
public InvalidEmailException(String email, String reason) {
super("Invalid email '" + email + "': " + reason);
this.email = email;
this.reason = reason;
}
public String getEmail() {
return email;
}
public String getReason() {
return reason;
}
}
public class FlexibleException {
public static void main(String[] args) {
// Using constructor 1
try {
throw new InvalidEmailException("Email validation failed");
} catch (InvalidEmailException e) {
System.out.println("Error 1: " + e.getMessage());
}
// Using constructor 3 - with details
try {
validateEmail("invalid-email");
} catch (InvalidEmailException e) {
System.out.println("\nError 2: " + e.getMessage());
System.out.println("Email: " + e.getEmail());
System.out.println("Reason: " + e.getReason());
}
}
public static void validateEmail(String email)
throws InvalidEmailException {
if (!email.contains("@")) {
throw new InvalidEmailException(email, "Missing @ symbol");
}
if (!email.contains(".")) {
throw new InvalidEmailException(email, "Missing domain");
}
}
}
// Output:
// Error 1: Email validation failed
//
// Error 2: Invalid email 'invalid-email': Missing @ symbol
// Email: invalid-email
// Reason: Missing @ symbol

🛒 Real-World Example: E-Commerce System

ECommerceExceptions.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Custom exceptions for e-commerce
class ProductNotFoundException extends Exception {
private String productId;
public ProductNotFoundException(String productId) {
super("Product not found: " + productId);
this.productId = productId;
}
public String getProductId() {
return productId;
}
}
class OutOfStockException extends Exception {
private String productName;
private int requestedQuantity;
private int availableQuantity;
public OutOfStockException(String productName, int requested, int available) {
super(productName + " is out of stock. Requested: " +
requested + ", Available: " + available);
this.productName = productName;
this.requestedQuantity = requested;
this.availableQuantity = available;
}
public int getAvailableQuantity() {
return availableQuantity;
}
}
class InvalidQuantityException extends Exception {
public InvalidQuantityException(String message) {
super(message);
}
}
class Product {
private String id;
private String name;
private int stock;
private double price;
public Product(String id, String name, int stock, double price) {
this.id = id;
this.name = name;
this.stock = stock;
this.price = price;
}
public void purchase(int quantity)
throws InvalidQuantityException, OutOfStockException {
if (quantity <= 0) {
throw new InvalidQuantityException(
"Quantity must be positive. Got: " + quantity
);
}
if (quantity > stock) {
throw new OutOfStockException(name, quantity, stock);
}
stock -= quantity;
System.out.println("✓ Purchased " + quantity + " x " + name);
System.out.println(" Total: $" + (price * quantity));
System.out.println(" Remaining stock: " + stock);
}
public String getName() {
return name;
}
}
public class ECommerceExceptions {
public static void main(String[] args) {
Product laptop = new Product("P001", "Laptop", 5, 999.99);
// Test 1: Valid purchase
try {
System.out.println("=== Test 1: Valid Purchase ===");
laptop.purchase(2);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
// Test 2: Out of stock
try {
System.out.println("\n=== Test 2: Out of Stock ===");
laptop.purchase(10);
} catch (OutOfStockException e) {
System.out.println("❌ " + e.getMessage());
System.out.println("Suggestion: Order " +
e.getAvailableQuantity() + " units instead");
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
// Test 3: Invalid quantity
try {
System.out.println("\n=== Test 3: Invalid Quantity ===");
laptop.purchase(-1);
} catch (InvalidQuantityException e) {
System.out.println("❌ " + e.getMessage());
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
// Output:
// === Test 1: Valid Purchase ===
// ✓ Purchased 2 x Laptop
// Total: $1999.98
// Remaining stock: 3
//
// === Test 2: Out of Stock ===
// ❌ Laptop is out of stock. Requested: 10, Available: 3
// Suggestion: Order 3 units instead
//
// === Test 3: Invalid Quantity ===
// ❌ Quantity must be positive. Got: -1

⚡ Unchecked Custom Exceptions

UncheckedExceptions.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Extending RuntimeException makes it unchecked
class InvalidConfigurationException extends RuntimeException {
private String configKey;
public InvalidConfigurationException(String configKey, String message) {
super("Invalid configuration for '" + configKey + "': " + message);
this.configKey = configKey;
}
public String getConfigKey() {
return configKey;
}
}
class ConfigManager {
// No need for 'throws' clause - it's unchecked!
public String getConfig(String key) {
if (key == null || key.isEmpty()) {
throw new InvalidConfigurationException(
key, "Key cannot be null or empty"
);
}
// Simulate config lookup
if (key.equals("database.url")) {
return "jdbc:mysql://localhost:3306/mydb";
}
throw new InvalidConfigurationException(
key, "Configuration key not found"
);
}
}
public class UncheckedExceptions {
public static void main(String[] args) {
ConfigManager config = new ConfigManager();
// No try-catch required (but recommended)
String dbUrl = config.getConfig("database.url");
System.out.println("Database URL: " + dbUrl);
// This will throw exception
try {
config.getConfig("invalid.key");
} catch (InvalidConfigurationException e) {
System.out.println("\nError: " + e.getMessage());
System.out.println("Missing key: " + e.getConfigKey());
}
}
}
// Output:
// Database URL: jdbc:mysql://localhost:3306/mydb
//
// Error: Invalid configuration for 'invalid.key': Configuration key not found
// Missing key: invalid.key
// Key difference:
// - Extends Exception = Checked (must declare with throws)
// - Extends RuntimeException = Unchecked (optional handling)

✅ Real-World Example: Validation Framework

ValidationFramework.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Base validation exception
class ValidationException extends Exception {
private String fieldName;
private Object invalidValue;
public ValidationException(String fieldName, Object value, String reason) {
super("Validation failed for '" + fieldName +
"': " + reason + " (value: " + value + ")");
this.fieldName = fieldName;
this.invalidValue = value;
}
public String getFieldName() {
return fieldName;
}
public Object getInvalidValue() {
return invalidValue;
}
}
// Specific validation exceptions
class TooShortException extends ValidationException {
public TooShortException(String fieldName, String value, int minLength) {
super(fieldName, value,
"Must be at least " + minLength + " characters");
}
}
class TooLongException extends ValidationException {
public TooLongException(String fieldName, String value, int maxLength) {
super(fieldName, value,
"Must not exceed " + maxLength + " characters");
}
}
class InvalidFormatException extends ValidationException {
public InvalidFormatException(String fieldName, String value, String format) {
super(fieldName, value,
"Must match format: " + format);
}
}
class UserValidator {
public void validateUsername(String username) throws ValidationException {
if (username == null) {
throw new ValidationException("username", null, "Cannot be null");
}
if (username.length() < 3) {
throw new TooShortException("username", username, 3);
}
if (username.length() > 20) {
throw new TooLongException("username", username, 20);
}
if (!username.matches("[a-zA-Z0-9_]+")) {
throw new InvalidFormatException(
"username", username, "letters, numbers, and underscore only"
);
}
}
public void validatePassword(String password) throws ValidationException {
if (password == null) {
throw new ValidationException("password", null, "Cannot be null");
}
if (password.length() < 8) {
throw new TooShortException("password", "****", 8);
}
}
}
public class ValidationFramework {
public static void main(String[] args) {
UserValidator validator = new UserValidator();
testValidation(validator, "alice", "SecurePass123");
testValidation(validator, "ab", "SecurePass123");
testValidation(validator, "user@123", "SecurePass123");
testValidation(validator, "validuser", "123");
}
public static void testValidation(UserValidator validator,
String username, String password) {
System.out.println("\n=== Validating User ===");
System.out.println("Username: " + username);
System.out.println("Password: " + password);
try {
validator.validateUsername(username);
validator.validatePassword(password);
System.out.println("✓ Validation passed!");
} catch (TooShortException e) {
System.out.println("❌ " + e.getMessage());
System.out.println(" Field: " + e.getFieldName());
} catch (TooLongException e) {
System.out.println("❌ " + e.getMessage());
} catch (InvalidFormatException e) {
System.out.println("❌ " + e.getMessage());
} catch (ValidationException e) {
System.out.println("❌ " + e.getMessage());
}
}
}
// Output:
// === Validating User ===
// Username: alice
// Password: SecurePass123
// ✓ Validation passed!
//
// === Validating User ===
// Username: ab
// Password: SecurePass123
// ❌ Validation failed for 'username': Must be at least 3 characters (value: ab)
// Field: username
//
// === Validating User ===
// Username: user@123
// Password: SecurePass123
// ❌ Validation failed for 'username': Must match format: letters, numbers, and underscore only (value: user@123)
//
// === Validating User ===
// Username: validuser
// Password: 123
// ❌ Validation failed for 'password': Must be at least 8 characters (value: ****)

🔑 Key Concepts

Extending Exception

For checked exceptions that must be handled

class MyException extends Exception

Extending RuntimeException

For unchecked exceptions (optional handling)

class MyException extends RuntimeException

Custom Constructors

Add constructors for flexibility

MyException(String message, int errorCode)

Additional Fields

Store extra error context

private int errorCode; private String details;

Best Practices

  • Name custom exceptions with 'Exception' suffix (e.g., InsufficientFundsException)
  • Extend Exception for checked, RuntimeException for unchecked
  • Provide multiple constructors for flexibility
  • Include meaningful default messages
  • Add fields to store error context (error codes, timestamps, etc.)
  • Override toString() if you need custom formatting

💼 Interview Tips

  • Know when to create custom exceptions vs using built-in ones
  • Understand the difference between checked and unchecked custom exceptions
  • Be able to explain the constructor chain with super()
  • Know common patterns: InvalidXException, XNotFoundException, etc.
  • Understand how custom exceptions integrate with try-catch
  • Be prepared to discuss exception class hierarchies