Careeroza
Error Handling with ErrorDecoder
What Happens on 4xx and 5xx
By default, any non-2xx response makes Feign throw FeignException (subclasses like FeignException.NotFound, FeignException.ServiceUnavailable). Letting that bubble up gives the client a confusing 500. Translate remote errors into your own exceptions with an ErrorDecoder:
public class ProductErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultDecoder = new ErrorDecoder.Default();
@Override
public Exception decode(String methodKey, Response response) {
return switch (response.status()) {
case 404 -> new ProductNotFoundException("Product not found (" + methodKey + ")");
case 409 -> new OutOfStockException("Not enough stock");
case 503 -> new ServiceUnavailableException("product-service is unavailable");
default -> defaultDecoder.decode(methodKey, response);
};
}
}
// register it only for this client (class NOT annotated with @Configuration,
// otherwise it would apply to every Feign client)
class ProductClientConfig {
@Bean
ErrorDecoder productErrorDecoder() {
return new ProductErrorDecoder();
}
}
@FeignClient(name = "product-service", path = "/api/products", configuration = ProductClientConfig.class)
public interface ProductClient { /* ... */ }Map to a Clean API Response
@RestControllerAdvice
class OrderExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
ProblemDetail notFound(ProductNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.UNPROCESSABLE_ENTITY,
"One of the products in your order no longer exists");
}
@ExceptionHandler(ServiceUnavailableException.class)
ProblemDetail unavailable(ServiceUnavailableException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.SERVICE_UNAVAILABLE,
"We could not place your order right now. Please retry in a minute.");
}
}Example
curl -i -X POST localhost:8080/api/orders -H "Content-Type: application/json" \
-d '{"userId": 42, "productId": 999, "quantity": 1}'Example output
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{"type":"about:blank","title":"Unprocessable Entity","status":422,
"detail":"One of the products in your order no longer exists","instance":"/api/orders"}