Skip to content

ksevindik/rest-error-handling

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

REST Error Handling

A comprehensive Spring Boot-based REST error handling framework that implements standardized error responses according to RFC 9457 - Problem Details for HTTP APIs.

Overview

This project provides a unified, consistent error handling mechanism for REST APIs by implementing the Problem Details standard. It ensures that all error responses, whether from API endpoints or framework-level errors (like 404 for non-existent resources), follow a standardized JSON structure.

Key Features

  • RFC 9457 Compliance: Error responses follow the Problem Details for HTTP APIs specification
  • Unified Error Handling: Consistent error format across all endpoints and error scenarios
  • Internationalization (i18n): Localized error messages based on client's preferred locale
  • Extensible Design: Pluggable providers for error types, exception mappings, and error messages
  • Declarative Configuration: YAML-based configuration for error types, exception mappings, and localized messages
  • Comprehensive Coverage: Handles both application exceptions and framework-level errors (404, 500, etc.)

Implementation Benefits

RFC 9457 Compliance

  • Standardized Format: All error responses follow the Problem Details specification
  • Machine Readable: Clients can programmatically handle errors using the type field
  • Human Readable: title and detail fields provide clear error descriptions
  • Extensible: Additional fields can be added without breaking the standard

Internationalization (i18n)

  • Automatic Localization: Error messages automatically localized based on client's Accept-Language header
  • Intelligent Fallback: Automatic fallback to English when requested locale is unavailable
  • Dynamic Discovery: Runtime discovery of available locale files from classpath
  • Flexible Configuration: Simply drop new error-messages_<locale>.yml files to add language support

Unified Error Handling

  • Consistent Format: Same error structure for all types of errors (application, validation, framework)
  • Content Type: Uses application/problem+json media type as specified in RFC 9457
  • Status Code Mapping: HTTP status codes are consistently mapped to appropriate problem types

Extensibility

  • Provider Pattern: Easy to extend with custom error type, exception mapping and error message providers
  • Declarative Configuration: Error types, messages and mappings can be defined in YAML files
  • No Code Changes: New error types, messages and mappings can be added without modifying controllers

Technologies Used

  • Spring Boot 3.5.5: Web framework and auto-configuration
  • Java 17: JDK Version 17
  • Kotlin 1.9.25: Primary development language
  • SnakeYAML: Direct YAML parsing for configuration files
  • Jackson: JSON serialization with Kotlin module
  • Spring Validation: Input validation support
  • JUnit 5: Testing framework

Core Components

Error Model

The Error class implements the RFC 9457 problem details structure:

open class Error(
    open val type: URI? = null,          // Problem type identifier
    open val status: Int? = null,        // HTTP status code  
    open val method: String? = null,     // HTTP method
    open val instance: URI? = null,      // URI identifying the specific occurrence
    open val title: String? = null,      // Human-readable summary
    open var detail: String? = null,     // Human-readable explanation
    open val trace: String? = null       // Stack trace for debugging
)

Key RFC 9457 Mappings:

  • type: Maps to the RFC's "type" field - URI identifying the problem type
  • status: Maps to the RFC's "status" field - HTTP status code
  • instance: Maps to the RFC's "instance" field - URI identifying the specific occurrence
  • title: Maps to the RFC's "title" field - Short, human-readable summary
  • detail: Maps to the RFC's "detail" field - Human-readable explanation

Extensions:

  • method: HTTP method that caused the error
  • trace: Stack trace for debugging (non-production environments)

Error Controllers

GlobalErrorAdvice

Handles both application-level exceptions and framework-level errors (404, 500, etc.) using @RestControllerAdvice and by extending ResponseEntityExceptionHandler class:

  • Catches exceptions thrown by controllers and services
  • Handles validation errors with detailed parameter information
  • Provides stack traces for debugging
  • Performs localization based on the client's preferred language

ErrorController

Handles errors from requests handled by Servlets that are outside the control of Spring Web MVC by extending Spring's BasicErrorController. Similar to what GlobalErrorAdvice does, it basically creates an error object when a particular Servlet sends an error response via HttpServletResponse.sendError method call.

Extension Mechanisms

ErrorTypeProvider Interface

Defines how error types are provided and resolved:

interface ErrorTypeProvider {
    fun getErrorTypes(): Map<String, ErrorType>
    fun getErrorType(name: String): ErrorType
    fun getErrorType(statusCode: HttpStatusCode): ErrorType
    fun getErrorType(status: HttpStatus): ErrorType
}

Available Implementations:

  1. YamlErrorTypeProvider (Default): Loads error types from error-types.yml configuration file

    • Provides declarative error type configuration
    • Automatically active when error-handling.provider=yaml or by default
  2. DefaultErrorTypeProvider (Fallback): Provides hardcoded error types

    • Contains built-in programmatically defined error types
    • Active when error-handling.provider=default

ExceptionToErrorMappingProvider Interface

Defines how exceptions are mapped to error responses:

interface ExceptionToErrorMappingProvider {
    fun getExceptionToErrorMappings(): Map<KClass<out Exception>, ExceptionToErrorMapping>
    fun getExceptionToErrorMapping(ex: Exception): ExceptionToErrorMapping
}
  1. YamlExceptionToErrorMappingProvider (Default): Loads exception to error type mappings from error-mappings.yml configuration file
    • Provides declarative error type configuration
    • Automatically active when error-handling.provider=yaml or by default
  2. DefaultExceptionToErrorMappingProvider (Fallback): Provides hardcoded exception to error type mappings:
    • Contains built-in programmatically defined exception to error type mappings
    • Active when error-handling.provider=default Extension Point: Implement custom providers for declarative configuration

ErrorMessageProvider Interface

Provides localized error messages for internationalization (i18n):

interface ErrorMessageProvider {
    fun getAllErrorMessages(): Map<Locale, Map<String, ErrorMessage>>
    fun getErrorMessages(locale: Locale): Map<String, ErrorMessage>
    fun getErrorMessage(errorType: String, locale: Locale): ErrorMessage?
    fun getErrorMessageWithFallback(errorType: String, locale: Locale, defaultLocale: Locale = Locale.ENGLISH): ErrorMessage?
    fun getAvailableLocales(): Set<Locale>
}

Available Implementation:

  1. YamlErrorMessageProvider (Default): Loads localized error messages from error-messages_<locale>.yml files
    • Supports multiple languages and locales
    • Provides automatic fallback to default locale (English)
    • Discovers locale files automatically from classpath
  2. DefaultErrorMessageProvider (Fallback): Provides hardcoded error messages.
    • Contains built-in programmatically defined error messages
    • Active when error-handling.provider=default

Provider Configuration

YAML Providers (Default):

# application.yml (or omit for default behavior)
error-handling:
  provider: yaml

Default Providers (Fallback):

# application.yml
error-handling:
  provider: default

Configuration Files

error-types.yml

Defines available error types with their URIs and HTTP status codes:

error-types:
  - error-uri: "http://www.example.com/errors/general/unknown-error"
    status-code: 500
  - error-uri: "http://www.example.com/errors/general/validation-error" 
    status-code: 400
  - error-uri: "http://www.example.com/errors/foo-service/not_found"
    status-code: 404
  - error-uri: "http://www.example.com/errors/foo-service/foo-creation-not-allowed"
    status-code: 403

error-mappings.yml

Maps exception types to error responses:

error-mappings:
  - exception-type: com.example.errorhandling.sample.FooNotFoundException
    error-type: "http://www.example.com/errors/foo-service/not_found"
  - exception-type: com.example.errorhandling.sample.FooCreationNotAllowedException
    error-type: "http://www.example.com/errors/foo-service/foo-creation-not-allowed"
  - exception-type: java.lang.Exception
    error-type: "http://www.example.com/errors/general/unknown-error"

error-messages_<locale>.yml (Internationalization)

Provides localized error messages for different languages:

File Naming Convention: error-messages_<locale>.yml

  • English: error-messages_en.yml
  • Turkish: error-messages_tr.yml
  • And more locales supported...

Example Structure:

error-messages:
  - error-type: "http://www.example.com/errors/foo-service/not_found"
    title: "Foo Not Found"
    detail: "The requested Foo resource could not be found with the provided parameters."
  - error-type: "http://www.example.com/errors/general/validation-error"
    title: "Validation Error"
    detail: "The provided data is invalid. Please check your input and try again."
  - error-type: "http://www.example.com/errors/general/unknown-error"
    title: "Unexpected Error"
    detail: "An unexpected error occurred while processing your request."
  - error-type: "http://www.example.com/errors/foo-service/foo-creation-not-allowed"
    title: "Foo Creation Not Allowed"
    detail: "You do not have the necessary permissions to create a new Foo resource."

Sample Error Responses

Standard Error Response

{
  "type": "http://www.example.com/errors/foo-service/not_found",
  "status": 404,
  "method": "GET", 
  "instance": "/api/foo/123",
  "title": "Foo Not Found",
  "detail": "The requested Foo resource could not be found with the provided parameters.",
  "trace": "com.example.errorhandling.sample.FooNotFoundException: Foo not found..."
}

Validation Error Response

{
  "type": "http://www.example.com/errors/general/validation-error",
  "status": 400,
  "method": "POST",
  "instance": "/api/foo",
  "title": "Validation Error", 
  "detail": "The provided data is invalid. Please check your input and try again.",
  "invalidParams": [
    {
      "name": "email",
      "reason": "must be a well-formed email address"
    },
    {
      "name": "age", 
      "reason": "must be greater than or equal to 0"
    }
  ]
}

Demo Application

A complete demo application is available in the demo/ directory that showcases how to use this REST Error Handling framework in a real Spring Boot application.

Key Demo Features:

  • Live Examples: Working endpoints that demonstrate custom exceptions, validation errors, and framework errors
  • Internationalization: Shows localized error messages based on Accept-Language header
  • YAML Configuration: Sample configuration files for error types, mappings, and localized messages
  • RFC 9457 Compliance: All error responses follow the Problem Details specification

Quick Demo:

  1. Build and run the demo:

    # Build the main library
    ./gradlew publishToMavenLocal
    
    # Run the demo application
    cd demo
    ./gradlew bootRun
  2. Test different error scenarios:

    # Custom exception
    curl "http://localhost:8080/foo?name=xxx"
    
    # Validation error
    curl "http://localhost:8080/foo"
    
    # Localized error (Turkish)
    curl "http://localhost:8080/foo?name=xxx" -H "Accept-Language: tr"

For detailed demo instructions and examples, see demo/README.md.


This framework provides a robust, standards-compliant foundation for REST API error handling that can be easily extended and customized for specific application needs.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages