DevNotes
Support
Exception Handling · Chapter 04

Custom Exceptions

Designing meaningful domain exceptions — checked vs unchecked, constructors, exception chaining and Spring Boot handling.

◷ 5 min read§ 14 sections? 8 interview questions{ } 20 code examples

#What is a Custom Exception?

A custom exception is a user-defined exception created by extending an existing Java exception class. Instead of throwing generic exceptions like:

Java
throw new RuntimeException("Something went wrong");

we create meaningful exceptions:

Java
throw new UserNotFoundException("User not found");

This makes code:

  • More readable
  • Easier to debug
  • Easier to maintain
  • Better for API clients
  • Better for logging

#Why Do We Need Custom Exceptions?

Imagine an e-commerce application:

Java
public void placeOrder() {
}

Possible failures: user not found, product out of stock, payment failed, invalid coupon, address not found.

Using generic exceptions — throw new RuntimeException("Error"); — is not helpful. Instead:

Java
throw new ProductOutOfStockException();
throw new PaymentFailedException();
throw new InvalidCouponException();

Now anyone reading the code immediately understands the problem.

Real-World Example

Bad design:

Java
if (user == null) {
    throw new RuntimeException("Error");
}

Good design:

Java
if (user == null) {
    throw new UserNotFoundException("User ID 101 not found");
}

#Creating a Custom Checked Exception

A checked exception extends Exception.

Java
public class InvalidFileException extends Exception {

    public InvalidFileException(String message) {
        super(message);
    }
}

Usage:

Java
public void uploadFile() throws InvalidFileException {
    throw new InvalidFileException("Invalid File Format");
}

The compiler forces handling.

#Creating a Custom Unchecked Exception

Most enterprise applications use this approach.

Java
public class UserNotFoundException extends RuntimeException {

    public UserNotFoundException(String message) {
        super(message);
    }
}

Usage:

Java
throw new UserNotFoundException("User not found");

No throws required.

#Checked vs Unchecked Custom Exceptions

CheckedUnchecked
Extends ExceptionExtends RuntimeException
Compiler checksCompiler ignores
Must handleOptional
Used for recoverable situationsUsed for programming or business errors

Which One Should We Use?

Checked exceptions — reading files, database export/import, external APIs, recoverable failures:

Java
class InvalidFileException extends Exception {
}

Unchecked exceptions — invalid input, business validation, missing data, programming mistakes, entity not found:

Java
class UserNotFoundException extends RuntimeException {
}

#Constructor Variations

Java
public class UserException extends RuntimeException {

    // Default
    public UserException() {
    }

    // Message
    public UserException(String message) {
        super(message);
    }

    // Message + cause
    public UserException(String message, Throwable cause) {
        super(message, cause);
    }

    // Cause only
    public UserException(Throwable cause) {
        super(cause);
    }
}

#Exception Chaining

Suppose an IOException occurs. Instead of exposing it directly:

Java
catch (IOException e) {
    throw new RuntimeException(e);
}

create a meaningful exception:

Java
catch (IOException e) {
    throw new FileUploadException("Unable to upload file", e);
}

Now you have a business exception with the original IOException preserved. This is called exception chaining.

Why Exception Chaining?

Without chaining
File Upload Failed
root cause lost
With chaining
FileUploadException
caused by IOException
caused by Disk Full
Debugging becomes much easier

Accessing the Cause

Java
try {
    // ...
} catch (UserException e) {
    Throwable cause = e.getCause();
}

#Designing Meaningful Exception Names

Poor namesGood names
MyExceptionUserNotFoundException
ApplicationExceptionOrderNotFoundException
ErrorExceptionPaymentFailedException
CustomExceptionInsufficientBalanceException
InvalidCouponException
DuplicateEmailException
EmployeeAlreadyExistsException

#Exception Hierarchy Design

  • ApplicationException
    • BusinessException
      • UserNotFoundException
      • ProductOutOfStockException
      • PaymentFailedException
      • CouponExpiredException
This makes exception handling easier

#Spring Boot Integration

Throwing from a service

Java
public User getUser(Long id) {
    return repository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
}

A very common interview question.

Global Exception Handling

Spring Boot commonly handles custom exceptions centrally:

Java
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handle(UserNotFoundException ex) {
        return ResponseEntity
                .status(404)
                .body(ex.getMessage());
    }
}
Result
HTTP 404
User not found

Multiple Custom Exceptions

UserNotFoundException, ProductNotFoundException, OrderNotFoundException, PaymentFailedException, SeatUnavailableException, BookingExpiredException — large applications often have dozens of domain-specific exceptions.

#Common Mistakes

  • Using RuntimeException everywhere — instead of throw new RuntimeException("Something failed"), use throw new PaymentFailedException("Payment gateway timeout").
  • Losing the original exception — throw new FileUploadException("Upload failed") inside a catch drops the cause; use throw new FileUploadException("Upload failed", e).
  • Very generic names — prefer CustomerAlreadyExistsException over ApplicationException.
Java
// Wrong — cause is lost
catch (IOException e) {
    throw new FileUploadException("Upload failed");
}

// Correct — cause preserved
catch (IOException e) {
    throw new FileUploadException("Upload failed", e);
}

#Best Practices

  • One exception per business scenario.
  • Extend RuntimeException for business validation failures.
  • Extend Exception for recoverable operations when callers are expected to handle them.
  • Include meaningful messages.
  • Preserve the original cause.
  • Keep exception classes lightweight.
  • Avoid placing business logic inside exception classes.

#Interview Questions

Q1What is a custom exception?

A user-defined exception created by extending Exception or RuntimeException to represent a specific business or application error.

Q2Why do we need custom exceptions?

They improve readability, debugging, logging and API design, and clearly communicate the reason for failure.

Q3Should custom exceptions extend Exception or RuntimeException?

It depends:

  • Extend Exception for recoverable situations.
  • Extend RuntimeException for business rules and programming errors.
Q4What is exception chaining?

Wrapping one exception inside another while preserving the original cause:

Java
throw new FileUploadException("Upload failed", e);
Q5Why should we preserve the cause?

To retain the original stack trace and root cause, making debugging much easier.

Q6Can a custom exception have multiple constructors?

Yes — typically default, message, cause, and message + cause.

Q7Why are custom exceptions commonly used in Spring Boot?

They work well with centralized exception handling (@ControllerAdvice), allowing business errors to be mapped to meaningful HTTP responses.

Q8Should exception classes contain business logic?

No. Exception classes should only represent error conditions and optionally store error-related information.

#Chapter Summary

  • What custom exceptions are and why they are needed
  • Checked vs unchecked custom exceptions
  • Creating custom exception classes and constructors
  • Exception chaining and preserving the original cause
  • Exception hierarchy design
  • Spring Boot integration
  • Common mistakes and enterprise best practices