Part 2:Exception Architecture: Custom Exception Design & Global Handling

🏗️ Part 2: Architecture – Custom Exception Design & Global Handling

🏗️ Exception Architecture

Building Robust Exception Handling with Custom Classes & Global Management

🎯 What You’ll Learn

  • BaseServiceException with automatic origin tracking
  • Service-specific exception design patterns
  • GlobalExceptionHandler centralized management
  • ServiceResponse wrapper for unified API responses

🏗️ Architecture Overview

A well-architected exception handling system has four key components:

🧱 BaseServiceException

Generic foundation with origin tracking

🏷️ Service-Specific Exceptions

Typed exceptions for different services

🌐 GlobalExceptionHandler

Centralized exception management

🎁 ServiceResponse Wrapper

Unified API response format

🧱 Base Exception Architecture

Generic Base Class with Origin Tracking

@Getter
public abstract class BaseServiceException extends RuntimeException {
    private final ErrorCode errorCode;
    private final String origin;
    private final LocalDateTime timestamp;

    public BaseServiceException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
        this.timestamp = LocalDateTime.now();

        // Automatically capture className.methodName for debugging
        StackTraceElement[] stackTrace = this.getStackTrace();
        this.origin = stackTrace.length > 0
                ? stackTrace[0].getClassName() + "." + stackTrace[0].getMethodName()
                : "UnknownOrigin";
    }

    public BaseServiceException(ErrorCode errorCode, String customMessage) {
        super(customMessage);
        this.errorCode = errorCode;
        this.timestamp = LocalDateTime.now();

        StackTraceElement[] stackTrace = this.getStackTrace();
        this.origin = stackTrace.length > 0
                ? stackTrace[0].getClassName() + "." + stackTrace[0].getMethodName()
                : "UnknownOrigin";
    }

    public BaseServiceException(ErrorCode errorCode, Throwable cause) {
        super(errorCode.getMessage(), cause);
        this.errorCode = errorCode;
        this.timestamp = LocalDateTime.now();

        StackTraceElement[] stackTrace = this.getStackTrace();
        this.origin = stackTrace.length > 0
                ? stackTrace[0].getClassName() + "." + stackTrace[0].getMethodName()
                : "UnknownOrigin";
    }
}

💡 Why Origin Tracking Matters

  • Rapid debugging: Instantly know where exceptions originated
  • Production monitoring: Track exception patterns by service/method
  • Team collaboration: Clear ownership of exception sources
  • Performance insights: Identify bottlenecks and failure points

🏷️ Service-Specific Exception Classes

Member Service Exceptions

// User-related exceptions
public class UserNotFoundException extends BaseServiceException {
    public UserNotFoundException(MemberErrorCode errorCode) {
        super(errorCode);
    }

    public UserNotFoundException(MemberErrorCode errorCode, String email) {
        super(errorCode, String.format("User with email '%s' not found", email));
    }
}

public class DuplicateException extends BaseServiceException {
    public DuplicateException(MemberErrorCode errorCode) {
        super(errorCode);
    }

    public DuplicateException(MemberErrorCode errorCode, String email) {
        super(errorCode, String.format("User with email '%s' already exists", email));
    }
}

public class ValidationException extends BaseServiceException {
    public ValidationException(MemberErrorCode errorCode) {
        super(errorCode);
    }

    public ValidationException(MemberErrorCode errorCode, String field, String value) {
        super(errorCode, String.format("Invalid %s: '%s'", field, value));
    }
}

// System-related exceptions
public class ServiceException extends BaseServiceException {
    public ServiceException(MemberErrorCode errorCode) {
        super(errorCode);
    }

    public ServiceException(MemberErrorCode errorCode, Throwable cause) {
        super(errorCode, cause);
    }
}

🌐 GlobalExceptionHandler Implementation

Centralized Exception Management

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    private final MeterRegistry meterRegistry;

    public GlobalExceptionHandler(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    // Helper method for consistent responses
    private ResponseEntity<ServiceResponse<?>> buildErrorResponse(
            ErrorCode errorCode, BaseServiceException ex, String serviceName) {

        // Log based on severity
        logException(ex, errorCode);

        // Track metrics
        trackExceptionMetrics(errorCode, serviceName);

        return new ResponseEntity<>(
            ServiceResponse.builder()
                .success(false)
                .message(ex.getMessage())
                .errorCode(errorCode.name())
                .data(Map.of(
                    "origin", ex.getOrigin(),
                    "service", serviceName,
                    "timestamp", ex.getTimestamp(),
                    "severity", errorCode.getSeverity().name()
                ))
                .timestamp(LocalDateTime.now())
                .build(),
            errorCode.getHttpStatus()
        );
    }

    // 1. DEPENDENCY CHECKS (5xx)
    @ExceptionHandler(ServiceException.class)
    public ResponseEntity<ServiceResponse<?>> handleServiceException(
            ServiceException ex) {
        return buildErrorResponse(ex.getErrorCode(), ex, "member-service");
    }

    // 2. BUSINESS VALIDATION (4xx)
    @ExceptionHandler(DuplicateException.class)
    public ResponseEntity<ServiceResponse<?>> handleDuplicationException(
            DuplicateException ex) {
        return buildErrorResponse(ex.getErrorCode(), ex, "member-service");
    }

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ServiceResponse<?>> handleUserNotFoundException(
            UserNotFoundException ex) {
        return buildErrorResponse(ex.getErrorCode(), ex, "member-service");
    }

    // 3. UNKNOWN ERRORS (5xx)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ServiceResponse<?>> handleUnknownException(Exception ex) {
        log.error("Unexpected error occurred", ex);

        ServiceException serviceEx = new ServiceException(MemberErrorCode.INTERNAL_ERROR, ex);
        return buildErrorResponse(MemberErrorCode.INTERNAL_ERROR, serviceEx, "member-service");
    }
}

🎁 ServiceResponse Wrapper

Unified Response Format

@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class ServiceResponse<T> {
    private boolean success;
    private String message;
    private String errorCode;
    private T data;
    private LocalDateTime timestamp;

    // Factory methods for success responses
    public static <T> ServiceResponse<T> success(String message, T data) {
        return ServiceResponse.<T>builder()
            .success(true)
            .message(message)
            .data(data)
            .timestamp(LocalDateTime.now())
            .build();
    }

    public static <T> ServiceResponse<T> success(T data) {
        return ServiceResponse.<T>builder()
            .success(true)
            .message("Operation completed successfully")
            .data(data)
            .timestamp(LocalDateTime.now())
            .build();
    }

    // Factory methods for error responses
    public static <T> ServiceResponse<T> error(String message, String errorCode) {
        return ServiceResponse.<T>builder()
            .success(false)
            .message(message)
            .errorCode(errorCode)
            .timestamp(LocalDateTime.now())
            .build();
    }

    // Validation helpers
    public boolean isSuccess() {
        return success;
    }

    public boolean isError() {
        return !success;
    }
}

🎯 Part 2 Key Takeaways

✅ BaseServiceException

  • Automatic origin tracking
  • Correlation IDs for request tracing
  • Contextual information for debugging

✅ ErrorCode Interface

  • Severity levels for appropriate logging
  • Categories for better classification
  • Extensible design for service needs

✅ GlobalExceptionHandler

  • Priority-based exception handling
  • Consistent response formats
  • Automatic metrics tracking

✅ ServiceResponse Wrapper

  • Standard success/error format
  • Rich error context information
  • Easy client-side error handling

🔗 Continue Your Journey

Leave a Comment

Your email address will not be published. Required fields are marked *