We have a microservice architecture. Each service exposing data through Rest. All controllers are set up using Spring:
#RestController
#RequestMapping(path = "foobar")
public class UiController {
#PostMapping("foo")
public ResponseEntity<Foo> addFoo(#RequestBody final FooDto fooDto) {
Foo fromDb = adminService.addFoo(converterToModel.convert(fooDto);
return ResponseEntity.ok(converterToDto.convert(fromDb));
}
If for some reason fooDto can't be added to the database. A custom Exception is thrown:
#ResponseStatus(value = HttpStatus.CONFLICT)
public class FooAlreadyAssignedException extends RuntimeException {
public FooAlreadyAssignedException(String msg) {
super("The following foos are already assigned to foobar: " + msg);
}
}
In Postman you see the following JSON after the Exception above is thrown
{
"timestamp": 1508247298817,
"status": 409,
"error": "Conflict",
"exception": "com.foo.exception.FooCodeAlreadyExistsException",
"message": "A foo with code: foo already exists",
"path": "/foo/foobar"
}
We have 4 different services like these all set up the same way.
Our UI is made in Angular 4 and makes REST calls to our Gateway. The Gateway is the connection between the microservices and the UI. It also exposes a REST endpoint. It's also implemented with Spring. I added a picture for clarification:
architecture
"edit: I see that I didn't complete the arrows. Of course all data is passed back up to the UI"
The problem
The Gateway uses a RestTemplate to call the APIs of the microservices
when a custom Exception is thrown in the microservice the Gateway returns this:
{
"timestamp": "2017-10-16T15:30:03.456Z",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.web.client.HttpClientErrorException",
"message": "409 null",
"path": "/v1/printstations"
}
My original response a HttpStatus.conflict (status = 409) seems to be wrapped in a status 500 message by the Gateway. I don't want this behavior, I want it to pass the original message to the UI.
Any ideas on how to control this behavior?
Notes
I have tested with Postman that if you access the microservice directly it returns the 409 with the message written in the custom Exception
I have already tried overriding Springs ResponseErrorHandler but was not able to find a suitable solution that way.
In gateway code where spring rest template is calling your microservices, I would recommend catching HttpClientErrorException and then create your own exception class like ApiException as in below example, this way you will be able to pass the exact exception which is thrown from the microservices:
catch (org.springframework.web.client.HttpClientErrorException e) {
throw new ApiException(e.getMessage(), e, e.getRawStatusCode(), e.getResponseHeaders(),
e.getResponseBodyAsString(), fullURIPath, null);
}
where ApiException has a constructor like below:
public ApiException(String message, Throwable throwable, int code, Map<String, List<String>> responseHeaders,
String responseBody, String requestURI, String requestBody) {
super(message, throwable);
this.code = code;
this.responseHeaders = responseHeaders;
this.responseBody = responseBody;
this.requestURI = requestURI;
this.requestBody = requestBody;
}
Issue can be closed.
The solution was to map the exception that happened in the microservice to a valid ResponseEntity in the Gateway, so that the RestTemplate in the Gateway wouldn't repackage the error in a 500 server error.
We did this by creating a #ControllerAdvice class
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {HttpClientErrorException.class})
protected ResponseEntity<Object> handleConflict(HttpClientErrorException ex, WebRequest request) {
return handleExceptionInternal(ex, ex.getResponseBodyAsString(),
new HttpHeaders(), ex.getStatusCode(), request);
}
}
This results in a ResponseEntity with the correct HttpStatus from the Exception in the microservice and a JSON body containing the message needed by the front-end.
Related
In my last project, there was a requirement for throwing exceptions when the request body contains extra parameters.
If the request will be like
{
"test":"body",
"name":"HR 1",
"location":"Location"
}
where test parameter is unnecessary and I've to return a response that should be like
{
"timestamp": "2022-05-07T00:13:59.144657",
"status": "500",
"error": "Internal Server Error",
"message": "test : must not be provided",
"path": "/api/departments/HR"
}
I've shared the answer. How I handled it.
In the application.properties I added this line.
spring.jackson.deserialization.fail-on-unknown-properties=true
This helps us to make deserialization fail on unknown properties and throw an exception which we can handle using handleHttpMessageNotReadable
create controller advice to handle exceptions
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity("Your Response Object", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
That's the solution.
I am experiencing an issue while a mandatory field is not filled, the following exception is displayed in the logs:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Lets say I have an object CodeRequest that contains an attribute as follows:
#NotBlank(message = "payloadFormatIndicator.required")
#Size(max = 2, message = "payloadFormatIndicator.size")
private String payloadFormatIndicator;
My controller have the object CodeRequest as parameter as shown below:
#PostMapping(value = "/dummy", produces = MediaType.IMAGE_PNG_VALUE)
public ResponseEntity<BufferedImage> generateQRCode(#Valid #RequestBody CodeRequest paymentRequest) throws Exception {
log.debug("generateQRCode with the following request {}", paymentRequest);
return ResponseEntity.status(HttpStatus.OK).body(ipsPaymentService.generateQRCode(paymentRequest));
}
When I leave the mandatory field payloadFormatIndicator empty I expect to get an error message that payloadFormatIndicator.required is required in my response.
However, I get the following error message in the log:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
My exception handler is shown below:
#Slf4j
#ControllerAdvice
public class RestControllerExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
HttpStatus status, WebRequest request) {
log.error(exception.getMessage(), exception);
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST,
exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return new ResponseEntity<>(exceptionResponse, new HttpHeaders(), exceptionResponse.getHttpStatus());
}
It looks like because the method generateQRCode is returning ResponseEntity<BufferedImage> it is causing this issue because for the other methods on my controller, the exception handling is working fine.
I am using swagger to test the rest API and the content type is shown below:
Any idea how I can fix it pls?
The issue is because of producer media-type. The response only accept image/png, yet when there is an error media-type is application/json.
change your code like this,
#PostMapping(value = "/dummy", produces = "application/json, image/png")
I am developing a web application using Java and Spring Boot.
Specifically, I am reviewing some code written by other developers. I use Postman to make HTTP calls to my application.
There are cases where my application needs to inform the caller of certain situations. In this example, the developer of the application threw a JwtTokenException to the caller in case of IOException.
try {
myToken = methodThatObtainsAToken(tokenInput);
}catch(IOException ex) {
throw new JwtTokenException("Error 401", JwtStatusResponse.JWT_NOT_VALID); // TODO: cambiare
}
When something goes wrong here is what happens on POSTMAN:
I have so many other very similar situations in this code and I have to do the following thing: I have to replace the code throw new JwtTokenException so that it throws exceptions that make the caller understand (so I can see them with Postman) that an HTTP error has occurred (with some code).
In the example I wrote I wrote "Error 401" only as a string. There are other places in the code where I use "Error 500", "Error 302" and so on.. But these are just information strings, and do not correspond to the real error thrown to the client. How do I throw correct exceptions that raise "HTTP errors"?
One usually returns response entity with body object and status set to provide
interesting information:
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, trackDTO.getId().toString()))
.body(result);
Response Entity is org.springframework.http.ResponseEntotuy which provides a lot of convenience methods to setup all the headers and whatever is necessary.
If most of the exceptions are handled like in code you have posted and you don't want to change return types of methods, just replace them with
catch(Exception e){
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
which will give the following output
{
"timestamp": "2021-03-10T09:25:04.823+0000",
"status": 404,
"error": "Not Found",
"message": "Not Found",
"path": "/account"
}
if you'd like to get exception info just add
catch(Exception e){
throw new ResponseStatusException(HttpStatus.NOT_FOUND,null, e);
}
response:
{
...
"message": "404 NOT_FOUND; nested exception is java.lang.ArithmeticException",
...
}
note that it will be logged if you didn't declare your own exception resolver
2021-03-10 15:28:20.043 WARN 11392 --- [nio-8090-exec-2] .w.s.m.a.ResponseStatusExceptionResolver : Resolved [org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND "Not Found"; nested exception is java.lang.ArithmeticException]
Instead of null you could pass a string describing exception, which will replace "message" in json response.
From your attached screenshot. Your application returned 401 Unauthorized. I think you no longer need to pass the error code since you get it from the response itself. Exceptions do not return Http Status Code and you as a developer is responsible to telling your app which error code to return. For example in your RestController
#GetMapping(path = "{id}")
public ResponseEntity<ResponseModel> getEmployee(#PathVariable("id") String id) {
ResponseModel result = employeeService.findById(id);
return new ResponseEntity<>(result, HttpStatus.OK); // <--- THIS
}
And in your custom exception handler, you can do it like this.
NOTE: This triggers when you call from your Jwt Verifier Filter
#ExceptionHandler(value = ExpiredJwtException.class)
public ResponseEntity<Object> handleExpiredJwt(ExpiredJwtException e) {
ApiException apiException = new ApiException(e.getLocalizedMessage(), HttpStatus.UNAUTHORIZED);
return new ResponseEntity<>(apiException, UNAUTHORIZED);
}
How do I propogate an exception thrown in a call to a downstream service to the caller method?
I have a service that calculates something and throws an exception in case of error:
{
"timestamp": "2019-03-12T08:21:05.316+0000",
"status": 500,
"error": "Internal Server Error",
"message": "VAKUUTUSVUOSI.MAKSUEHTO is null or not numeric. Can't be added to due date.",
"path": "/rules/yk32/deducePaymentDueDate"
}
But the calling service displays this exception:
{
"timestamp": "2019-03-12T08:30:22.912+0000",
"status": 500,
"error": "Internal Server Error",
"message": "500 null",
"path": "/calculation/annual/payment"
}
How do I get the caller method also to display the message that the service throws "/rules/yk32/deducePaymentDueDate" instead of "Internal Server Error"?
Calling method:
LocalDate paymentDueDate = ykServiceAdapter.yk32DeducePaymentDueDate(requestDTO);
Calling function in the ykServiceadapter:
public LocalDate yk32DeducePaymentDueDate(Yk32RequestDTO requestDTO) {
ResponseEntity<LocalDate> re;
HttpEntity<?> entity = new HttpEntity<>(requestDTO);
try {
re = getRestTemplate().exchange(
buildServiceUrl(externalServiceConfig, RULE_YK32, DEDUCE_PAYMENT_DUEDATE),
HttpMethod.POST, entity,
new ParameterizedTypeReference<LocalDate>() {
});
return re.getBody();
} catch (HttpClientErrorException ex) {
if (HttpStatus.NOT_FOUND.equals(ex.getStatusCode())) {
return null;
} else {
throw ex;
}
}
}
You're working on two separate contexts, via HTTP.
What that means is the Exception generated by yk32DeducePaymentDueDate is transformed to an HTTP 500 response, which might mean the Exception message is used as response body.
Obviously, being that the original Exception gets lost during the HTTP call, RestTemplate is only able to create an HttpClientErrorException based on the HTTP status code
HttpClientErrorException.BadRequest
HttpClientErrorException.Conflict
HttpClientErrorException.Forbidden
HttpClientErrorException.Gone
HttpClientErrorException.MethodNotAllowed
HttpClientErrorException.NotAcceptable
HttpClientErrorException.NotFound
HttpClientErrorException.TooManyRequests
HttpClientErrorException.Unauthorized
HttpServerErrorException.InternalServerError
HttpServerErrorException.NotImplemented
...
In your case the instantiated Exception is
public static class InternalServerError extends HttpServerErrorException {
InternalServerError(String statusText, HttpHeaders headers, byte[] body, #Nullable Charset charset) {
super(HttpStatus.INTERNAL_SERVER_ERROR, statusText, headers, body, charset);
}
}
Only the Exception message might be recovered, if it has been transmitted in the response body.
You might want to look into a custom ResponseErrorHandler, where you can inspect the full HTTP response and react accordingly.
I'm building a SpringBoot microservice that calls another microservice and naturally want to use Hystrix and Feign clients, which are both included with Spring Cloud. I'm using version Camden.SR5.
For any timeouts, connection failures and 50x response codes from Feign, I want Hystrix to kick in and work as normal: tripping the circuit breaker and calling the fallback (if configured), etc. It does this by default, so I'm good.
But for 40x response codes, which include things like invalid entry, the wrong format of fields etc, I want Hystrix to propagate these exceptions to the caller, so I can handle them as I choose too. This isn't the default I've observed. How do you configure Hystrix/Feign to do this in Spring Cloud?
Out of the box using the following code:
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.hateoas.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#FeignClient(name = "dog-service", url = "http://...")
public interface DogsFeignClient {
#RequestMapping(method = RequestMethod.POST, path = "/dogs")
Resource<Dog> createDog(Dog dog);
}
Generates this exception, which doesn't lend itself to nicely passing that 40x response back to the caller:
com.netflix.hystrix.exception.HystrixRuntimeException: DogsFeignClient#createDog(Dog) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:805) ~[hystrix-core-1.5.6.jar:1.5.6]
....lines ommited for brevity....
Caused by: feign.FeignException: status 400 reading DogsFeignClient#createDog(Dog); content:
{
"errors" : [ {
"entity" : "Dog",
"property" : "numberOfLegs",
"invalidValue" : "3",
"message" : "All dogs must have 4 legs"
} ]
}
at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-9.3.1.jar:na]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-9.3.1.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-9.3.1.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.3.1.jar:na]
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108) ~[feign-hystrix-9.3.1.jar:na]
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:301) ~[hystrix-core-1.5.6.jar:1.5.6]
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:297) ~[hystrix-core-1.5.6.jar:1.5.6]
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.1.10.jar:1.1.10]
... 26 common frames omitted
I can of course look at the com.netflix.hystrix.exception.HystrixRuntimeException, cause field which contains a feign.FeignException and buried in the description is the JSON response itself, with line breaks and such. But the cause field of feign.FeignException is a reference to itself. Is there a way to get a deeper exception propagated instead of the HystrixRuntimeException?
Also is there a way to get the raw body included with the response from the downstream service, so I don't have to deconstruct the message field of the nested exception?
This can be achieved using a separate configuration, which will wrap 400's in a subclass of HystrixBadRequestException and throw them to the client code.
These exceptions don't affect the circuit breaker state - if the circuit is closed, it will remain closed, and if it's open, it will remain open.
#FeignClient(name = "dog-service",
url = "http://...",
configuration=FeignPropagateBadRequestsConfiguration.class)
public interface DogsFeignClient {
#RequestMapping(method = RequestMethod.POST, path = "/dogs")
Resource<Dog> createDog(Dog dog);
}
where FeignPropagateBadRequestsConfiguration is
#Configuration
public class FeignSkipBadRequestsConfiguration {
#Bean
public ErrorDecoder errorDecoder() {
return (methodKey, response) -> {
int status = response.status();
if (status == 400) {
String body = "Bad request";
try {
body = IOUtils.toString(response.body().asReader());
} catch (Exception ignored) {}
HttpHeaders httpHeaders = new HttpHeaders();
response.headers().forEach((k, v) -> httpHeaders.add("feign-" + k, StringUtils.join(v,",")));
return new FeignBadResponseWrapper(status, httpHeaders, body);
}
else {
return new RuntimeException("Response Code " + status);
}
};
}
}
and FeignBadResponseWrapper is
#Getter
#Setter
public class FeignBadResponseWrapper extends HystrixBadRequestException {
private final int status;
private final HttpHeaders headers;
private final String body;
public FeignBadResponseWrapper(int status, HttpHeaders headers, String body) {
super("Bad request");
this.status = status;
this.headers = headers;
this.body = body;
}
}
This is a bit of a hack, and you can get the response body only in ErrorDecoder, because after that the stream will be closed. But using this, you can throw the response data to client code without affecting the circuit:
try {
return dogsFeignClient.createDog(dog);
} catch (HystrixBadRequestException he) {
if (he instanceof FeignBadResponseWrapper) {
// obtain data from wrapper and return it to client
} else {
// return basic error data for other exceptions
}
}