Skip to content

Repository files navigation

API Errors

Small Spring WebFlux library that auto-configures JSON error responses for common API failures.

It provides:

  • EntityNotFoundException mapped to 404 NOT_FOUND
  • ValidationException mapped to 400 BAD_REQUEST
  • WebFlux binding errors and invalid enum values mapped to 400 BAD_REQUEST

Installation

Gradle Kotlin DSL

dependencies {
  implementation("io.github.kostack:api-errors:<version>")
}

Gradle Groovy DSL

dependencies {
  implementation 'io.github.kostack:api-errors:<version>'
}

Maven

<dependency>
  <groupId>io.github.kostack</groupId>
  <artifactId>api-errors</artifactId>
  <version>VERSION</version>
</dependency>

The package is a Spring Boot auto-configuration module. After it is on the classpath, Spring Boot registers the default handlers automatically.

Usage

Throw EntityNotFoundException when a requested resource cannot be found:

import io.github.kostack.api_errors.exception.EntityNotFoundException
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

@RestController
class UserController(
  private val users: UserRepository
) {
  @GetMapping("/users/{id}")
  suspend fun findUser(@PathVariable id: String): User =
    users.findById(id) ?: throw EntityNotFoundException(User::class, id)
}

Response:

{
  "message": "Entity not found with identifier 123 of type User",
  "violations": []
}

Throw ValidationException for domain validation errors:

import io.github.kostack.api_errors.dto.ApiValidation
import io.github.kostack.api_errors.exception.ValidationException

fun validateCreateUser(request: CreateUserRequest) {
  val violations =
    buildList {
      if (request.email.isBlank()) {
        add(ApiValidation.Violation(fieldId = "email", message = "must not be blank"))
      }
      if (request.age < 18) {
        add(
          ApiValidation.Violation(
            fieldId = "age",
            message = "must be greater than or equal to 18",
            metadata = mapOf("minimum" to 18)
          )
        )
      }
    }

  if (violations.isNotEmpty()) {
    throw ValidationException(violations)
  }
}

Response:

{
  "message": "Validation Failed",
  "violations": [
    {
      "fieldId": "email",
      "message": "must not be blank",
      "metadata": {}
    },
    {
      "fieldId": "age",
      "message": "must be greater than or equal to 18",
      "metadata": {
        "minimum": 18
      }
    }
  ]
}

Spring WebFlux binding errors are handled automatically. For example, request DTO validation failures are returned as ApiValidation violations:

import jakarta.validation.Valid
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.NotBlank
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

data class CreateUserRequest(
  @field:NotBlank
  @field:Email
  val email: String
)

@RestController
class CreateUserController {
  @PostMapping("/users")
  suspend fun createUser(@Valid @RequestBody request: CreateUserRequest): UserResponse {
    TODO("create user")
  }
}

If an enum request value cannot be parsed, the response includes the invalid value and valid enum constants:

{
  "message": "Validation Failed",
  "violations": [
    {
      "fieldId": "status",
      "message": "Invalid value 'disabled'. Valid values: ACTIVE, INACTIVE",
      "metadata": {}
    }
  ]
}

Customization

The default handlers back off when a bean of the same handler type already exists:

import io.github.kostack.api_errors.handler.WebExchangeBindExceptionHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class ApiErrorConfiguration {
  @Bean
  fun webExchangeBindExceptionHandler(): WebExchangeBindExceptionHandler =
    WebExchangeBindExceptionHandler()
}

For different response behavior, define your own @RestControllerAdvice for the exception type you want to handle.

License

This project is licensed under the MIT License. See LICENSE.

About

JSON error responses for common API failures

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages