DEV Community

Isaias Velasquez
Isaias Velasquez

Posted on

How to fazer exception handling limpo com ControllerAdvice

@ControllerAdvice e a forma mais limpa de centralizar o tratamento de erros em uma aplicacao Spring Boot. Em vez de espalhar try/catch por todos os controllers, voce trata tudo em um lugar so.

Crie uma classe com @ControllerAdvice:

@ControllerAdvice
class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException::class)
    fun handleNotFound(ex: ResourceNotFoundException, request: WebRequest):
            ResponseEntity<Map<String, Any>> {
        val body = mapOf(
            "error" to "Not Found",
            "message" to ex.message,
            "status" to 404
        )
        return ResponseEntity(body, HttpStatus.NOT_FOUND)
    }

    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun handleValidation(ex: MethodArgumentNotValidException):
            ResponseEntity<Map<String, Any>> {
        val erros = ex.bindingResult.fieldErrors.map {
            "${it.field}: ${it.defaultMessage}"
        }
        val body = mapOf(
            "error" to "Validation Failed",
            "details" to erros,
            "status" to 400
        )
        return ResponseEntity(body, HttpStatus.BAD_REQUEST)
    }
}
Enter fullscreen mode Exit fullscreen mode

Crie excecoes customizadas:

class ResourceNotFoundException(message: String) : RuntimeException(message)
class BusinessException(message: String) : RuntimeException(message)
Enter fullscreen mode Exit fullscreen mode

Uso no controller:

@RestController
class UsuarioController(private val service: UsuarioService) {

    @GetMapping("/usuarios/{id}")
    fun buscar(@PathVariable id: Long): ResponseEntity<Usuario> {
        val usuario = service.buscarPorId(id)
            ?: throw ResourceNotFoundException("Usuario $id nao encontrado")
        return ResponseEntity.ok(usuario)
    }
}
Enter fullscreen mode Exit fullscreen mode

Quando o usuario nao existe, o ResourceNotFoundException e lancado. O GlobalExceptionHandler captura e retorna um JSON limpo.

Para validacao de entrada com @Valid:

data class CriarUsuarioRequest(
    @field:NotBlank val nome: String,
    @field:Email val email: String,
    @field:Min(18) val idade: Int
)

@PostMapping("/usuarios")
fun criar(@Valid @RequestBody request: CriarUsuarioRequest): ResponseEntity<Usuario> {
    val usuario = service.criar(request)
    return ResponseEntity(usuario, HttpStatus.CREATED)
}
Enter fullscreen mode Exit fullscreen mode

Se a validacao falha, o MethodArgumentNotValidException e lancado e o handler devolve os erros campo a campo.

Para excecoes genericas nao mapeadas:

@ExceptionHandler(Exception::class)
fun handleGeneric(ex: Exception): ResponseEntity<Map<String, Any>> {
    val body = mapOf(
        "error" to "Internal Server Error",
        "message" to (ex.message ?: "Erro inesperado"),
        "status" to 500
    )
    return ResponseEntity(body, HttpStatus.INTERNAL_SERVER_ERROR)
}
Enter fullscreen mode Exit fullscreen mode

Monte a resposta padrao em uma data class:

data class ErrorResponse(
    val error: String,
    val message: String?,
    val status: Int,
    val timestamp: Long = System.currentTimeMillis()
)

@ExceptionHandler(BusinessException::class)
fun handleBusiness(ex: BusinessException): ResponseEntity<ErrorResponse> {
    val body = ErrorResponse(
        error = "Business Error",
        message = ex.message,
        status = 422
    )
    return ResponseEntity(body, HttpStatus.UNPROCESSABLE_ENTITY)
}
Enter fullscreen mode Exit fullscreen mode

Para mais de um tipo de excecao no mesmo handler:

@ExceptionHandler(IOException::class, DataAccessException::class)
fun handleInfra(ex: Exception): ResponseEntity<ErrorResponse> {
    return ResponseEntity(
        ErrorResponse("Infra Error", ex.message, 503),
        HttpStatus.SERVICE_UNAVAILABLE
    )
}
Enter fullscreen mode Exit fullscreen mode

Com @ControllerAdvice voce tem um unico ponto de tratamento de erros. O codigo dos controllers fica limpo e as respostas de erro seguem um padrao consistente.

That's all for now.
Thanks for reading!

Top comments (0)