Suppose I call a webservice with RestTemplate and it returns 500 status error with this body:
{
"timestamp": "2019-10-10T16:51:15Z",
"status": 500,
"error": "Internal Server Error",
"message": "Error occurred while retrieving entity with id: bb00b45c-9e17-4d75-a89a",
"path": "/api/service"
}
Currently RestTemplate exception message is something like:
Caused by: org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 null
at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:79)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:124)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:102)
Is there any way to include the body of the response in the RestTemplate exception message without using a custom error handler?
Thanks!
May be something like this (without a Custom Error Handler)
ObjectMapper mapper;
try {
ResponseEntity<User> response = restTemplate.exchange(url,
HttpMethod.POST, requestEntity, User.class);
} catch (HttpStatusCodeException e) {
List<String> header = e.getResponseHeaders().get("x-app-err-id");
String errorMessageId = "";
if (header != null && !header.isEmpty()) {
errorMessageId = header.get(0);
}
// You can get the body, but deserialise it using mapper into a POJO
ErrorResponseBody errorResponseBody = mapper.readValue(e.getResponseBodyAsString(),
ErrorResponseBody.class);
// You can re-throw it if you want or use the response body
throw new CustomException(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
Related
In my Service there was Jersey client implementation to call a rest API now I was migrating this code to RestTemplate.
In old code when there was a 401 error that comes as a response from the backend and I store the response in an object.
But when I migrated the code to RestTeplate the 401 is caught by HttpClientErrorException class so I am not able to get the response since the code flow goes to the catch block.
Jersey Client code
public Employees getEmployees1() throws MyException {
Employee employee=new Employee(23, "Test", "Test", "Test#test.com");
ClientResponse response=null;
try {
Client client = Client.create();
WebResource webResource = client.resource("http://localhost:8080/employees/");
response = webResource.accept("application/json")
.type("application/json").header("Authorization", "invalid Data").post(ClientResponse.class, employee);
}catch (RuntimeException e) {
logger.error("Runtime Error Occured -{} Response - {} ",e.getMessage(),response.getStatus());
throw new MyException("Unexpected Error Occured",e);
}catch (Exception e) {
logger.error("Some Error Occured -{} Response - {} ",e.getMessage(),response.getStatus());
throw new MyException("Unexpected Error Occured",e);
}
return response.readEntity(Employees.class);
}
RestTemplate Code
public Employees getEmployees() throws MyException {
Employee employee=new Employee(23, "Test", "Test", "Test#test.com");
HttpHeaders headers=new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add(HttpHeaders.AUTHORIZATION, "invalid Data");
ResponseEntity<Employees> response=null;
try {
response = this.restTemplate.exchange("http://localhost:8080/employees/", HttpMethod.POST, new HttpEntity<Employee>(employee,headers), Employees.class);
}catch (RuntimeException e) {
logger.error("Runtime Error Occured -{} Response - {} ",e.getMessage(),response.getStatusCode());
throw new MyException("Unexpected Error Occured",e);
}catch (Exception e) {
logger.error("Some Error Occured -{} Response - {} ",e.getMessage(),response.getStatusCode());
throw new MyException("Unexpected Error Occured",e);
}
return response.getBody();
}
By default RestTemplate throws an HttpStatusCodeException (a child of it) for all 400+ status codes - see DefaultResponseErrorHandler. You can change this behavior by setting your own implementation of ResponseErrorHandler to RestTemplate using setErrorHandler or if RestTemplate is constructed using RestTemplateBuilder - using errorHandler method of the builder.
I used the default ResponseErrorHandler, by using this it will bypass all the ResponseError exception
RestTemplate rt = restTemplateBuilder.errorHandler(new ResponseErrorHandler(){
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}})
.build();
I'm doing a onStatus implementation in my API when I use a webclient (Webflux) to call external API:
//Webclient Call
Flux<Movie> movies = webclient.get().uri(uriBuilder -> uriBuilder.path(api_url)
.build(author))
.retrieve()
.onStatus(HttpStatus::is4xxClientError,
response -> Mono.error(new AcceptHeaderNotsupportedException(response.statusCode().getReasonPhrase())))
.bodyToFlux(Movie.class)
//Global Handler Exception Class
public class GlobalExceptionHandler {
#ExceptionHandler(AcceptHeaderNotsupportedException.class)
public ResponseEntity<?> AcceptHeaderHandling(AcceptHeaderNotsupportedException exception){
ApiException apiException = new ApiException(HttpStatus.NOT_FOUND.value(), exception.getMessage());
return new ResponseEntity<>(ApiException, HttpStatus.NOT_FOUND);
}
}
//AcceptHeaderNotsupportedException Class
public class AcceptHeaderNotsupportedException extends RuntimeException{
public AcceptHeaderNotsupportedException(String message){
super(message);
}
}
//Api custom Exception
public class ApiCustomException{
private int code;
private String message;
}
I am testing a scenario webclient call that return a 406 error from downstream api. So i want to map the response to my object representation and give to my client (postman in this case).
{
code: 406,
"message": error from downstream api
}
but i am getting to client
{
"timestamp": "2021-08-29T14:31:00.944+00:00",
"path": "path",
"status": 406,
"error": "Not Acceptable",
"message": "Could not find acceptable representation",
"requestId": "ba66698f-1",
"trace": "org.springframework.web.server.NotAcceptableStatusException: 406 NOT_ACCEPTABLE \"Could not find acceptable representation\"\n\tat ....}
In case of a 404 error from downstream API the mapping response works fine.
{
code: 404,
"message": not found
}
My question is if i am doing .onStatus(HttpStatus::is4xxClientError should not work for both (404, 406 or other responde code with 4xx ?
I already tried disabling the Default Error handling of Spring boot w/c throws
{
"timestamp": 1575346220347,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.web.client.HttpClientErrorException",
"message": "401 Unauthorized",
"path": "/auth/login" }
By adding the ff. Config.
#SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class)
and
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
But I'm getting a bunch of HTML formatted Response instead of the JSON response it should be getting from the server.
You can Use Controller Advice to make a global exception handler. Inside the ControllerAdvice class, you can use #ExceptionHandler annotation to handle exceptions. Here is a good article about ControllerAdvice. https://medium.com/#jovannypcg/understanding-springs-controlleradvice-cd96a364033f
I was not able to disable SpringBoots automatic handling of Error responses however I was able to get the proper JSON Error Response by wrapping my Rest Template request in a try catch and using a library in the rest template as it turns out there is a bug in Rest Template that wouldn't allow me to retrieve the Response body.
From
private final RestTemplate restTemplate = new RestTemplate();
To
private final RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
Try-Catch Wrapping
ResponseEntity resp = null;
try{
resp = restTemplate.postForEntity(hostUrl+loginUrl, request,Object.class);
}catch(HttpClientErrorException e) {
ErrorDto result = new ObjectMapper().readValue(e.getResponseBodyAsString(), ErrorDto.class);
return new ResponseEntity<>(result, e.getStatusCode());
}
ErrorDto.java
#JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDto {
#JsonProperty("Message")
private String message;
#JsonProperty("Reason")
private String reason;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}
HttpClientErrorException always produces the following result for me:
HttpClientErrorException: 400 null
... and the null part is what worries me. Shouldn't this be the place where the message of the server-side exception is supposed to be?
I checked the source code of the HTTP client to see where the client-side exception is thrown. It looks like this:
throw new HttpClientErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
Debugging this call revealed that response.getStatusText() is null in my case.
My question is: How do you design your ResponseEntity on the server-side such that the HTTP client finds the server-side exception message in response.getStatusText() instead of null?
Here is my Exception
#ExceptionHandler({ MyCustomException.class })
public ResponseEntity<String> handleException(final HttpServletRequest
req, final MyCustomException e) {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-type", "text/plain");
String body = e.toString();
return new ResponseEntity<>(body, headers, HttpStatus.BAD_REQUEST);
}
In my project I have to create a chat box. For that I have to use Open-fire server. I am having the service for creating users in this server.
I am facing the problem when I am trying to access the openfire service from my spring boot application.
I am have created the model for the user also created the service and provided implementation to it.
This is my model class,
public class OpenFireUser {
private String firstname;
private String username;
private String password;
private String email;
<----getters and setters--->
}
This is my service,
public UserCreationResponse createOpenFireUser(String authorization,User createUser) {
OpenFireUser user= new OpenFireUser();
user.setEmail(createUser.getEmail());
user.setFirstname(createUser.getFirstname().toLowerCase());
user.setPassword("Passw0rd");
user.setUsername(createUser.getUsername().toLowerCase());
UserCreationResponse response=new UserCreationResponse();
RestTemplate restTemplate = new RestTemplate();
try{
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Authorization", authorization);
headers.add("Content-Type", "application/json");
headers.add("Accept", "application/json");
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<OpenFireUser> requestObject = new HttpEntity<OpenFireUser>(user, headers);
ResponseEntity<String> responseEntity=restTemplate.postForEntity(OPENFIRE_REST_ENDPOINT, requestObject,String.class);
int statusCode = responseEntity.getStatusCodeValue();
if(statusCode==201){
response.setResponseCode(statusCode);
return response;
}else{
response.setResponseCode(MessageConstant.ResponseCode.ProcessFail.value());}
return response;
}catch(HttpClientErrorException clientErr){
response.setUserMessage(clientErr.getMessage());
response.setResponseCode(clientErr.getStatusCode().value());
response.setResponseMessage(MessageConstant.CodeMessage.ProcessFail.value());
return response;
}
catch(Exception e)
{
response.setResponseCode(MessageConstant.ResponseCode.ProcessFail.value());
response.setResponseMessage(MessageConstant.CodeMessage.ProcessFail.value());
return response;
}
}
This is controller code for calling service,
#RequestMapping(value = "/createOpenFireUser", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
public UserCreationResponse createOpenFireUser(#RequestBody User createUser) {
logger.debug("Entering inside createOpenFireUser(#RequestBody OpenFireUser createUser) method");
//logger.debug("Create user request : {}" , createUserRequestEntity);
return userService.createOpenFireUser("authorizationKey",createUser);
}
while sending data from postman I am getting error like,
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of com.exelatech.printshop.model.User out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.exelatech.printshop.model.User out of START_ARRAY token
at [Source: java.io.PushbackInputStream#6c88daca; line: 1, column: 1]
2018-07-20 15:59:13.535 WARN 9988 --- [nio-9090-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of model.User out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of model.User out of START_ARRAY token
at [Source: java.io.PushbackInputStream#6c88daca; line: 1, column: 1]
On postman I am getting response like,
"timestamp": 1532082553781,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not deserialize instance of model.User out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of model.User out of START_ARRAY token\n at [Source: java.io.PushbackInputStream#6c88daca; line: 1, column: 1]",
Can anyone help me to solve my issue?
Error was in sending object through postman.
Previously I was sending request object like,
[
{
"firstname" : "Jyoti",
"username" : "JyotiNH",
"password":"Passw0rd",
"email" : "jyoti.kanor#exelaonline.com"
}
]
which is object.
But my method receiving parameter as array of strings,
So when I sent request with object like following structure, it successfully created the user.
{
"firstname" : "Jyoti",
"username" : "JyotiNH",
"password":"Passw0rd",
"email" : "jyoti.kanor#exelaonline.com"
}