So I am build an mvc application using Jersey. A method that accepts Path parameters (#PathParam).
If a custom exception is thrown (ExampleException) then a 404 Not Found response is returned using an exception mapper.
#Provider
public class ExampleExceptionMapper implements ExceptionMapper<ExampleException> {
#Override
public Response toResponse(ExampleException ex) {
return Response.status(Status.NOT_FOUND).entity("Not Found - " + ex.getMessage()).build();
}
}
However, I am implementing #FormParam's so a user POSTs to the server. The same exact exception is raised, but instead I should return a 400 Bad Request response. Without modifying the exception how would I be able to make the exception mapper return the proper response code?
Simplest way is create multiple ExceptionMappers, each for specific subclass of ExampleException.
But you want to have the same exception for both cases and decide whether to throw 404 for GET/PathParam and POST/FormParam, you can inject the request into the mapper and check what method it is:
#Provider
public class ExampleExceptionMapper implements ExceptionMapper<ExampleException> {
#Context Request request;
#Override
public Response toResponse(ExampleException ex) {
if ("POST".equals(requset.getMethod()))
return Response.status(Status.BAD_REQUEST).build();
else
return Response.status(Status.NOT_FOUND).entity("Not Found - " + ex.getMessage()).build();
}
}
If you want to decide by PathParams, you can inject UriInfo:
#Provider
public class ExampleExceptionMapper implements ExceptionMapper<ExampleException> {
#Context UriInfo info;
#Override
public Response toResponse(ExampleException ex) {
if (info.getPathParameters().isEmpty())) //please make better condition based on your needs
return Response.status(Status.BAD_REQUEST).build();
else
return Response.status(Status.NOT_FOUND).entity("Not Found - " + ex.getMessage()).build();
}
}
Related
I am using OpenFeign client in Spring Boot without using Ribbon or Eureka. I created a custom error decoder which handles response errors as intended but connection refused errors seem to bypass my custom decoder.
P.S. When my remote service is up, I can make requests and receive responses.
I am new to Java and Spring and I am wondering if I need to wrap all my calls with try catch, or adding my custom error handler should be catching the error since it seems cleaner to handle all errors in one place
public class FeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
//handle with custom exception
}
if (response.status() >=500) {
//handle with custom exception
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
#Configuration
public class FeignConfig {
//other beans here
#Bean
public ErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
}
So I've been using Spring and Java for a while to build microservices. I am concerned by the way I am currently handling service layer results which uses "business exception"
Controller
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<?> checkout(#RequestBody CheckoutRequest body) {
try {
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
catch (UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
catch (CustomBusinessException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
}
Service
#Service
public class PurchaseService {
// ...
public DTO doCheckout(CheckoutRequest request) {
// this one calls another microservice
if (!isUserValid(request.userId)) {
// current handling of business rules violation (1)
throw new UnauthorizedException("User not valid");
}
if (request.total < 10) {
// current handling of business rules violation (2)
throw new CustomBusinessException("Minimum checkout at 20 dollars");
}
// ... do actual checkout
return new DTO(someDTOData);
}
}
I was comfortable at using this "pattern" because I do not need to "if" the business result in the controller level to return the appropriate HttpStatusCode, but since I've found some articles saying that exception is expensive specifically in Java, I doubt what I was doing is good for the long run.
Is there another correct way to gracefully handles the business result layer?
The problem with ResponseEntity in Spring is that they are typed with the result object you want to return when the endpoint is called successfully, so you can't return another body different from the happy path one, that in your case would be SameDTO. One way to address this issue is to use ? as the type of the response entity, as you have done but it is not the most recommended way.
So the best way to do this is precisely to use exceptions when there is a situation when you can't return the expected object and you have to return another object or status code, but instead of using a try-catch in the controller you should use an exception handler (Controller Advice) https://www.baeldung.com/exception-handling-for-rest-with-spring.
This controller advice would catch any exception thrown in your application and depending on the exception type it could return a different response class or status code without affecting the main controller. One example of how can be your controller advice would be:
#ControllerAdvice
public class ErrorHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleInternal(final RuntimeException ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ex.getMessage());
}
#ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ResponseDto> identityClientException(UnauthorizedException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(e.getMessage());
}
#ExceptionHandler(CustomBusinessException.class)
public ResponseEntity<ResponseDto> identityClientException(CustomBusinessException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
And your controller woulb be much more clean without exception handling logic:
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<SomeDTO> checkout(#RequestBody CheckoutRequest body){
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
}
java 8, spring, rest
I am trying to capture the Response that comes from exception mapper, and do something with it in the caller which throws the exception. Thanks.
#Provider
public class CustomerExceptionHandler implements ExceptionMapper<CustomerException>
{
#Override
public Response toResponse(CustomerException exception)
{
return Response.status(Status.BAD_REQUEST).entity(CustomerException.getMessage()).build();
}
}
public class CustomerException extends Exception implements Serializable
{
private static final long serialVersionUID = 1L;
public CustomerException() {
super();
}
public CustomerException(String msg) {
super(msg);
}
public CustomerException(String msg, Exception e) {
super(msg, e);
}
}
public class ExceptionDemo{
public void getExceptionResponse(){
//do something
throw new CustomerException("Something is wrong");// CustomerExceptionHandler is going to return me a Response, how can I capture the response here?
//capture response and do something with it
}
}
I'm not sure ExceptionMappers work in the way you think they do.
When some code in the endpoint throws an exception, and this exception percolates all the way out of the endpoint and back into the container itself (Spring in this case), then the registered ExceptionMappers are consulted to see if they match the thrown exception, and the relevant one's public Response toResponse(T e) {} method is called to transform it into a Response.
The ExceptionMapper doen't get called as part of your endpoint code, and you won't be able to take action based on its resultant Response because it hasn't yet been called. You just need to throw the exception out of the endpoint.
My Hystrix/Feign app makes calls to other web services.
I would like to propagate error codes/messages from these web services.
I implemented ErrorDecoder, which correctly decodes exceptions returned and rethrow them.
Unfortunately these Exceptions are wrapped by HystrixRuntimeException and the JSON returned in not what I want (generic error message, always 500 http status).
Most likely I need an ExceptionMapper, I created one like this:
#Provider
public class GlobalExceptionHandler implements
ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable e) {
System.out.println("ABCD 1");
if(e instanceof HystrixRuntimeException){
System.out.println("ABCD 2");
if(e.getCause() != null && e.getCause() instanceof HttpStatusCodeException)
{
System.out.println("ABCD 3");
HttpStatusCodeException exc = (HttpStatusCodeException)e.getCause();
return Response.status(exc.getStatusCode().value())
.entity(exc.getMessage())
.type(MediaType.APPLICATION_JSON).build();
}
}
return Response.status(500).entity("Internal server error").build();
}
}
Unfortunately this code is not being picked-up by my application (debug statements are not visible in logs).
How could I register it with my Application?
I couldn't make use of an ExceptionMapper.
I solved this problem using ResponseEntityExceptionHandler.
Here is the code:
#EnableWebMvc
#ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(HystrixRuntimeException.class)
#ResponseBody
ResponseEntity<String> handleControllerException(HttpServletRequest req, Throwable ex) {
if(ex instanceof HystrixRuntimeException) {
HttpStatusCodeException exc = (HttpStatusCodeException)ex.getCause();
return new ResponseEntity<>(exc.getResponseBodyAsString(), exc.getStatusCode());
}
return new ResponseEntity<String>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Here,my requirement is that i want separate code in my application for exception handling,i saw a nice option of spring there using #controller advice to handle exceptions globally.
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
But there i want to cutomization there,like proper dynamic messages,own error code. so how can i do this,i am new to spring boot and even i don't have knowledge of spring.Need basic example.
You can come up with a class like this to capture information to be sent in response in case of exception:-
public class APIResponse {
int errorCode;
String description;
String someInformation;
// any other information that you want to send back in case of exception.
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public APIResponse handleConflict(DataIntegrityViolationException exception) {
APIResponse response = createResponseFromException(exception);
return response;
}
}
In your controller advice class:-
Have the return type APIResponse instead of void.
The handler method can have the exception raised as the argument.
Using the exception object to create the APIResponse object.
Put #ResponseBody on the handler method.