Python Custom Exception Handling

Learn how to create your own exceptions using custom exception classes and handle real-world business rules safely in Python applications.

What is a Custom Exception?

A custom exception is a user-defined exception class created to represent a specific error or business rule in an application.

Python provides many built-in exceptions such as ValueError, TypeError, KeyError, and FileNotFoundError. However, real applications often need errors that are specific to their own business logic.

Built-in Exception

Python-provided errors such as ValueError, TypeError and ZeroDivisionError.

Custom Exception

Developer-created exception classes representing application-specific errors.

raise

Used to intentionally trigger an exception when a business rule is violated.

try-except

Used to catch and handle the custom exception without crashing the application.

1. Basic Custom Exception

A custom exception can be created by inheriting from Python's built-in Exception class.

Python
class MyCustomError(Exception):
    pass


try:
    raise MyCustomError("Something went wrong")
except MyCustomError as e:
    print("Error:", e)
Output:
Error: Something went wrong

Here, MyCustomError is a user-defined exception.

2. Why Create Custom Exceptions?

Custom exceptions make application errors easier to understand and allow developers to separate different business rules.

Situation Possible Custom Exception
Insufficient bank balance InsufficientBalanceError
Invalid age InvalidAgeError
Product out of stock OutOfStockError
Invalid employee ID InvalidEmployeeError
Payment failure PaymentFailedError
3. Custom Exception with a Message

A custom exception can receive a meaningful error message.

Python
class InvalidAgeError(Exception):
    pass


age = 15

try:
    if age < 18:
        raise InvalidAgeError("Age must be 18 or above")
except InvalidAgeError as e:
    print("Error:", e)
Output:
Error: Age must be 18 or above
4. Real-Time Example: Bank Account

A bank application can create a custom exception when a customer tries to withdraw more money than the available balance.

Withdrawal Request
→
Check Balance
→
Raise Error
→
Handle Safely
Python
class InsufficientBalanceError(Exception):
    pass


class BankAccount:

    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientBalanceError(
                "Insufficient balance for this withdrawal"
            )

        self.balance -= amount
        print("Withdrawal successful")
        print("Remaining balance:", self.balance)


account = BankAccount(5000)

try:
    account.withdraw(7000)
except InsufficientBalanceError as e:
    print("Transaction failed:", e)
Output:
Transaction failed: Insufficient balance for this withdrawal
5. Custom Exception with Constructor

A custom exception can have its own constructor so that it can store additional information.

Python
class ProductNotFoundError(Exception):

    def __init__(self, product_id):
        self.product_id = product_id
        super().__init__(
            f"Product {product_id} was not found"
        )


try:
    raise ProductNotFoundError(101)
except ProductNotFoundError as e:
    print(e)
    print("Product ID:", e.product_id)
Output:
Product 101 was not found
Product ID: 101
6. Real-Time Example: Employee Validation

An HR application may reject an employee record if the salary or employee age does not satisfy company rules.

Python
class InvalidEmployeeError(Exception):
    pass


def validate_employee(name, age, salary):

    if age < 18:
        raise InvalidEmployeeError(
            "Employee must be at least 18 years old"
        )

    if salary < 15000:
        raise InvalidEmployeeError(
            "Salary is below the minimum allowed amount"
        )

    print("Employee validation successful")


try:
    validate_employee("Rahul", 25, 12000)
except InvalidEmployeeError as e:
    print("Validation Error:", e)
Output:
Validation Error: Salary is below the minimum allowed amount
7. Custom Exception with Multiple Attributes

Custom exceptions can store useful information such as an order ID, requested quantity, and available quantity.

Python
class OutOfStockError(Exception):

    def __init__(self, product, requested, available):
        self.product = product
        self.requested = requested
        self.available = available

        message = (
            f"{product}: requested {requested}, "
            f"but only {available} available"
        )

        super().__init__(message)


try:
    raise OutOfStockError("Laptop", 5, 2)
except OutOfStockError as e:
    print("Order Error:", e)
    print("Product:", e.product)
    print("Requested:", e.requested)
    print("Available:", e.available)
Output:
Order Error: Laptop: requested 5, but only 2 available
Product: Laptop
Requested: 5
Available: 2
8. Real-Time Example: Online Shopping

An e-commerce system can use a custom exception when a customer tries to order more products than are available.

Python
class OutOfStockError(Exception):
    pass


class Product:

    def __init__(self, name, stock):
        self.name = name
        self.stock = stock

    def buy(self, quantity):

        if quantity > self.stock:
            raise OutOfStockError(
                f"Only {self.stock} units of {self.name} are available"
            )

        self.stock -= quantity
        print("Purchase successful")
        print("Remaining stock:", self.stock)


product = Product("Headphones", 3)

try:
    product.buy(5)
except OutOfStockError as e:
    print("Order failed:", e)
Output:
Order failed: Only 3 units of Headphones are available
9. Custom Exception Hierarchy

Custom exceptions can inherit from another custom exception. This creates an exception hierarchy.

Python
class ApplicationError(Exception):
    pass


class PaymentError(ApplicationError):
    pass


class CardPaymentError(PaymentError):
    pass


try:
    raise CardPaymentError("Card payment was declined")
except CardPaymentError as e:
    print("Card Error:", e)
except PaymentError as e:
    print("Payment Error:", e)
except ApplicationError as e:
    print("Application Error:", e)
Output:
Card Error: Card payment was declined
10. Real-Time Example: Payment Processing

A payment system can define different custom exceptions for different payment failures.

Python
class PaymentError(Exception):
    pass


class PaymentDeclinedError(PaymentError):
    pass


class PaymentLimitError(PaymentError):
    pass


def process_payment(amount):

    if amount > 100000:
        raise PaymentLimitError(
            "Payment exceeds the allowed limit"
        )

    if amount == 0:
        raise PaymentDeclinedError(
            "Payment amount cannot be zero"
        )

    print("Payment processed successfully")


try:
    process_payment(150000)
except PaymentLimitError as e:
    print("Limit Error:", e)
except PaymentDeclinedError as e:
    print("Payment Error:", e)
Output:
Limit Error: Payment exceeds the allowed limit
11. Custom Exception with try-except-else-finally

Custom exceptions can be combined with all major exception handling blocks.

Python
class InvalidAmountError(Exception):
    pass


amount = 500

try:
    if amount <= 0:
        raise InvalidAmountError(
            "Amount must be greater than zero"
        )

except InvalidAmountError as e:
    print("Error:", e)

else:
    print("Amount is valid")

finally:
    print("Validation completed")
Output:
Amount is valid
Validation completed
12. Re-Raising a Custom Exception

Sometimes an exception is caught, logged, and then raised again so that another part of the application can handle it.

Python
class DatabaseError(Exception):
    pass


def connect_database():
    try:
        raise ConnectionError("Database connection failed")

    except ConnectionError as e:
        print("Logging database error:", e)
        raise DatabaseError("Unable to connect to database")


try:
    connect_database()
except DatabaseError as e:
    print("Application Error:", e)
Output:
Logging database error: Database connection failed
Application Error: Unable to connect to database
13. Using Custom Exceptions in Functions

Functions can raise custom exceptions when input does not satisfy application-specific rules.

Python
class InvalidUsernameError(Exception):
    pass


def register_user(username):

    if len(username) < 5:
        raise InvalidUsernameError(
            "Username must contain at least 5 characters"
        )

    print("User registered successfully")


try:
    register_user("Tom")
except InvalidUsernameError as e:
    print("Registration Error:", e)
Output:
Registration Error: Username must contain at least 5 characters
14. Real-Time Example: Login System

A login application can use custom exceptions for invalid credentials and account lock situations.

Python
class LoginError(Exception):
    pass


class InvalidCredentialsError(LoginError):
    pass


class AccountLockedError(LoginError):
    pass


def login(username, password):

    if username == "admin" and password == "12345":
        print("Login successful")
        return

    raise InvalidCredentialsError(
        "Invalid username or password"
    )


try:
    login("admin", "wrong")
except InvalidCredentialsError as e:
    print("Login Failed:", e)
except AccountLockedError as e:
    print("Account Locked:", e)
Output:
Login Failed: Invalid username or password
15. Custom Exception for File Validation

A file-processing application can define a custom exception when an uploaded file does not meet requirements.

Python
class InvalidFileError(Exception):
    pass


def validate_file(filename):

    if not filename.endswith(".csv"):
        raise InvalidFileError(
            "Only CSV files are allowed"
        )

    print("File accepted")


try:
    validate_file("employees.pdf")
except InvalidFileError as e:
    print("File Error:", e)
Output:
File Error: Only CSV files are allowed
16. Complete Real-Time Example: ATM System

This example combines multiple custom exceptions in an ATM application.

Python
class ATMError(Exception):
    pass


class InsufficientBalanceError(ATMError):
    pass


class InvalidPINError(ATMError):
    pass


class InvalidAmountError(ATMError):
    pass


class ATM:

    def __init__(self, pin, balance):
        self.pin = pin
        self.balance = balance

    def withdraw(self, entered_pin, amount):

        if entered_pin != self.pin:
            raise InvalidPINError("Incorrect PIN")

        if amount <= 0:
            raise InvalidAmountError(
                "Withdrawal amount must be greater than zero"
            )

        if amount > self.balance:
            raise InsufficientBalanceError(
                "Insufficient account balance"
            )

        self.balance -= amount

        print("Withdrawal successful")
        print("Remaining balance:", self.balance)


atm = ATM(1234, 10000)

try:
    atm.withdraw(1234, 12000)

except InvalidPINError as e:
    print("PIN Error:", e)

except InvalidAmountError as e:
    print("Amount Error:", e)

except InsufficientBalanceError as e:
    print("Balance Error:", e)
Output:
Balance Error: Insufficient account balance
17. Custom Exception with Inheritance

Custom exceptions follow normal Python inheritance rules. This allows common application errors to be caught together.

Python
class OrderError(Exception):
    pass


class InvalidOrderError(OrderError):
    pass


class OrderCancelledError(OrderError):
    pass


try:
    raise InvalidOrderError("Invalid order details")

except OrderError as e:
    print("Order problem:", e)
Output:
Order problem: Invalid order details
Tip: Catching the parent exception allows multiple related custom exceptions to be handled in one place.
18. Real-Time Example: Employee Leave Management

An employee management system can raise a custom exception when an employee requests more leave than available.

Python
class LeaveError(Exception):
    pass


def apply_leave(employee, requested_days, available_days):

    if requested_days > available_days:
        raise LeaveError(
            f"{employee} has only {available_days} leave days available"
        )

    print("Leave approved")


try:
    apply_leave("Anita", 8, 5)

except LeaveError as e:
    print("Leave Request Failed:", e)
Output:
Leave Request Failed: Anita has only 5 leave days available
19. Custom Exception with Error Code

Applications sometimes store an error code along with the human-readable error message.

Python
class APIError(Exception):

    def __init__(self, code, message):
        self.code = code
        self.message = message

        super().__init__(message)


try:
    raise APIError(404, "Customer not found")

except APIError as e:
    print("Error Code:", e.code)
    print("Message:", e.message)
Output:
Error Code: 404
Message: Customer not found
20. Custom Exception Best Practices
  • Inherit custom exceptions from Exception.
  • Give the exception a meaningful name ending with Error.
  • Use custom exceptions for application-specific business rules.
  • Provide clear and useful error messages.
  • Store important error information as exception attributes when necessary.
  • Create an exception hierarchy for related application errors.
  • Catch specific exceptions before broader parent exceptions.
  • Do not use custom exceptions as a replacement for normal program logic.
  • Use raise when a business rule is violated.
  • Keep exception classes simple and focused.
Important: Avoid creating custom exceptions when an existing built-in exception already represents the situation clearly.
21. Common Custom Exceptions in Real Applications
Application Custom Exception Purpose
Banking InsufficientBalanceError Insufficient funds
E-Commerce OutOfStockError Product unavailable
Authentication InvalidCredentialsError Invalid login details
HR InvalidEmployeeError Invalid employee information
Payments PaymentFailedError Payment processing failure
Files InvalidFileError Invalid uploaded file
Orders InvalidOrderError Invalid order details
22. Complete Real-Time Example: Online Order System

The following example demonstrates how custom exceptions can be used together in a realistic online shopping workflow.

Python
class OrderError(Exception):
    pass


class ProductNotFoundError(OrderError):
    pass


class OutOfStockError(OrderError):
    pass


class PaymentFailedError(OrderError):
    pass


products = {
    "Laptop": 2,
    "Phone": 5
}


def place_order(product, quantity, payment):

    if product not in products:
        raise ProductNotFoundError(
            f"{product} does not exist"
        )

    if quantity > products[product]:
        raise OutOfStockError(
            f"Only {products[product]} units are available"
        )

    if payment <= 0:
        raise PaymentFailedError(
            "Payment amount is invalid"
        )

    products[product] -= quantity

    print("Order placed successfully")
    print("Product:", product)
    print("Quantity:", quantity)


try:
    place_order("Laptop", 3, 50000)

except ProductNotFoundError as e:
    print("Product Error:", e)

except OutOfStockError as e:
    print("Stock Error:", e)

except PaymentFailedError as e:
    print("Payment Error:", e)
Output:
Stock Error: Only 2 units are available
23. Custom Exception Flow
User Input
→
Validate Rule
→
raise CustomError
→
except CustomError

The basic workflow is:

  1. Receive input or perform an application operation.
  2. Check a business-specific rule.
  3. Use raise when the rule is violated.
  4. Catch the custom exception using except.
  5. Display, log, or otherwise handle the error safely.
24. Important Custom Exception Rules
Rule Explanation
Inherit from Exception Custom exceptions should normally extend Exception.
Use meaningful names Names should clearly describe the error.
Use raise Raise the exception when the business rule fails.
Use try-except Catch and handle the custom exception safely.
Store useful data Exception attributes can hold IDs, amounts, codes, etc.
Create hierarchies Related exceptions can inherit from a common parent.
Keep errors specific Use the most specific exception that describes the problem.
Remember: Custom exceptions are especially useful when your application has business rules that cannot be clearly represented by Python's standard exceptions.

Python Custom Exception Handling

Learn to create meaningful application-specific errors with real-time examples.