java.time.format.DateTimeParseException handling in OpenFeign - spring-boot

I have multiple services, so I am using OpenFeign client like this:
#FeignClient(name = "identity-service")
public interface IdentityServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "/validate", produces = MediaType.APPLICATION_JSON_VALUE)
boolean validateId(#RequestParam String id);
}
I use a Feign Decoder:
#Component
public class FeignErrorDecoder implements ErrorDecoder {
Environment environment;
#Autowired
Jackson2ObjectMapperBuilder mapperBuilder;
#Override
public Exception decode(String methodKey, Response response) {
ExceptionMessage message = new ExceptionMessage();
try (InputStream bodyIs = response.body().asInputStream()) {
ObjectMapper mapper = mapperBuilder.build();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE);
message = mapper.readValue(bodyIs, ExceptionMessage.class);
} catch (IOException e) {
return new Exception(e.getMessage());
}
switch (response.status()) {
case 400:
return new ResponseStatusException(HttpStatus.BAD_REQUEST, message.getMessage() != null ? message.getMessage() : "Bad Request");
case 404:
return new ResponseStatusException(HttpStatus.NOT_FOUND, message.getMessage() != null ? message.getMessage() : "Not found");
default:
return errorDecoder.decode(methodKey, response);
}
}
I am trying to map the error into a custom error message for visibility:
public class ExceptionMessage {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
private LocalDateTime timestamp;
private HttpStatus status;
private String error;
private String message;
private String path;
}
However, I get consistent error that I haven't been able to work around:
2022-08-02 17:05:17.206 ERROR 87600 --- [o-auto-1-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause
java.lang.Exception: Cannot deserialize value of type `java.time.LocalDateTime` from String "2022-08-02T23:05:09.303+00:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2022-08-02T23:05:09.303+00:00' could not be parsed at index 19
I have to confess I have never seen that particular datetime format '2022-08-02T23:05:09.303+00:00' before, where does that come from?

Related

The recipient address <bd023813-ecb0-46ab-97b4-91b4ec64ad60> is not a 553 5.1.3 valid RFC-5321 address

I'm working on a web application using Angular. When I try to send a link to user to reset password, I got this error:
ERROR 17928 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.mail.MailSendException: Failed messages: javax.mail.SendFailedException: Invalid Addresses;
nested exception is:
com.sun.mail.smtp.SMTPAddressFailedException: 553-5.1.3 The recipient address <bd023813-ecb0-46ab-97b4-91b4ec64ad60> is not a 553 5.1.3 valid RFC-5321 address. u14sm4774711wrw.91 - gsmtp; message exceptions (1) are:
Failed message 1: javax.mail.SendFailedException: Invalid Addresses;
nested exception is:
com.sun.mail.smtp.SMTPAddressFailedException: 553-5.1.3 The recipient address <bd023813-ecb0-46ab-97b4-91b4ec64ad60> is not a 553 5.1.3 valid RFC-5321 address. u14sm4774711wrw.91 - gsmtp] with root cause
My controller:
#PostMapping("/checkemail")
public AccountResponse resetPasswordEmail(#RequestBody ResetPassword resetPassword) {
boolean result = this.UserS.ifEmailExist(resetPassword.getEmail());
AccountResponse accountresponse=new AccountResponse();
if (result) {
Mail mail = new Mail(resetPassword.getEmail(),UserCode.getCode());
emailservice.sendCodeByMail(mail);
accountresponse.setResultt(1);
} else {
accountresponse.setResultt(0);
}
return accountresponse;
}
Mail class:
public class Mail {
private String to;
private String code;
public Mail(String to, String code) {
super();
this.to = to;
this.code = code;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
Application.properties:
spring.mail.protocol=smtp
spring.mail.host=smtp.googlemail.com
spring.mail.port=587
spring.mail.username=myemail
spring.mail.password=mypassword
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable= true
spring.mail.properties.mail.smtp.starttls.required=true
EmailService:
public interface EmailService {
public void sendCodeByMail(Mail mail);
}
EmailServiceImpl:
#Service
public class EmailServiceImpl implements EmailService {
private JavaMailSender javaMailSender;
#Autowired
public EmailServiceImpl(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
#Override
#Async
public void sendCodeByMail(Mail mail) {
// TODO Auto-generated method stub
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom("example#gmail.com");
simpleMailMessage.setTo(mail.getCode());
simpleMailMessage.setSubject("Code Active");
simpleMailMessage.setText(mail.getCode());
javaMailSender.send(simpleMailMessage);
}
}
simpleMailMessage.setTo(mail.getCode());
must be:
simpleMailMessage.setTo(mail.getTo());

Mocking RestTemplate.exchange() method gives null value

Mocking RestTemplate.exchange() is not working. The Response of restTemplate.exchange() mocking gives null value at BDS Adapter class. My test case is failing with null pointer exception in BDSAdapter class. (response.getStatusCodeValue() gives null pointer exception..Mockito hints
Unused... -> at com..policydetails_adapters.BDSAdapterTest.getInsuranceHoldings(BDSAdapterTest.java:56)
[MockitoHint] ...args ok? -> at com.policydetails_adapters.BDSAdapter.fetchInsuranceDetails(BDSAdapter.java:77)
Below are my classes.
Test Class:
#RunWith(MockitoJUnitRunner.class)
public class BDSAdapterTest {
#InjectMocks
private BDSAdapter bdsAdapter;
#Mock
private BDSFetchInsuranceDetailsRequest bdsFetchInsuranceDetailsRequest;
#Mock
private RestTemplate restTemplate;
#Mock
Environment env;
#Test
public void getInsuranceHoldings() throws InsuranceHoldingsException {
Mockito.when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<BDSFetchInsuranceDetailsResponse>>any()))
.thenReturn(sampleBDSCustomerInsuranceHoldings());
BDSFetchInsuranceDetailsResponse bdsFetchInsuranceDetailsResponse = bdsAdapter.fetchInsuranceDetails("MBSG", "S6564318I", "1234", "007");
assertNotNull("response is not null", bdsFetchInsuranceDetailsResponse);
}
public static ResponseEntity<BDSFetchInsuranceDetailsResponse> sampleBDSCustomerInsuranceHoldings() {
BDSFetchInsuranceDetailsResponse bdsResponse = new BDSFetchInsuranceDetailsResponse();
Header header = new Header();
header.setChannelId("MBSG");
header.setMsgId("4aBE50ZrQtjVuXfTyALJ");
bdsResponse.setHeader(header);
ResponseEntity<BDSFetchInsuranceDetailsResponse> response = new ResponseEntity<BDSFetchInsuranceDetailsResponse>(bdsResponse, HttpStatus.ACCEPTED);
return response;
}
}
My Actual class
#Component
public class BDSAdapter {
#Autowired
RestTemplate restTemplate;
#Autowired
BDSFetchInsuranceDetailsRequest bdsFetchInsuranceDetailsRequest;
#Autowired
Environment env;
public BDSFetchInsuranceDetailsResponse fetchInsuranceDetails(String channelId, String customerId,
String insurerCode, String policyNumber) throws InsuranceHoldingsException {
BDSFetchInsuranceDetailsResponse bdsFetchInsuranceDetailsResponse = null;
try {
logger.info("Inside BDSAdapter::fetchInsuranceDetails");
Header header = new Header();
header.setMsgId(RandomStringUtils.randomAlphanumeric(20));
header.setChannelId(channelId);
bdsFetchInsuranceDetailsRequest.setHeader(header);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<BDSFetchInsuranceDetailsRequest> requestEntity = new HttpEntity<>(bdsFetchInsuranceDetailsRequest, requestHeaders);
ResponseEntity<BDSFetchInsuranceDetailsResponse> response = restTemplate.exchange(env.getProperty("bds_fetch_insurance_details_url"),HttpMethod.POST, requestEntity, BDSFetchInsuranceDetailsResponse.class);
if(response.getStatusCodeValue() == 204) {
throw new InsuranceHoldingsException(response.getStatusCode().toString(), "No Content");
}
bdsFetchInsuranceDetailsResponse = response.getBody();
} catch (Exception e) {
e.printStackTrace();
}
}
return bdsFetchInsuranceDetailsResponse;
}
}
Perhaps, because mock of BDSFetchInsuranceDetailsResponse will return not a class of BDSFetchInsuranceDetailsResponse but some mockclass
Arugments inside restTemplate.exchange() method should match. In this code env.getProperty("bds_fetch_insurance_details_url") returns null which does not match with String. So it gives null response.
Added the below statment before When(restTemplate.exchange(-,-,-,-).thenReturn(myResponse). It is working
`Mockito.when(env.getProperty("bds_fetch_insurance_details_url")).thenReturn("anyString")`;

Spring boot custom exception not getting printed in logs

I am trying to implement a custom exception for my Spring boot REST project. The custom exception gets called but shows no impact in the way error message is displayed.
This is the POJO I'm using for my custom errors:
public class ApiError {
private HttpStatus status;
private String message;
private List<String> errors;
public ApiError(HttpStatus status, String message, List<String> errors) {
super();
this.status = status;
this.message = message;
this.errors = errors;
}
public ApiError(HttpStatus status, String message, String error) {
super();
this.status = status;
this.message = message;
errors = Arrays.asList(error);
}
}
This is the exception handler I wrote:
#ControllerAdvice
#EnableWebMvc
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
System.out.println("Custom exception!!");
//List<String> details = new ArrayList<>();
//details.add(ex.getLocalizedMessage());
//System.out.println("Localize message:: "+ex.getLocalizedMessage());
// ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
//request.getDescription(false));
ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error", request.getDescription(false));
return new ResponseEntity<Object>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Additionally, I'm defined the following method within my controller:
#RequestMapping(value = "/model", params = "number", method = RequestMethod.GET, produces = "application/json")
List<Model> getModel(HttpServletRequest request,#RequestParam(value = "codeNumber") String number) throws Exception{
List<Model> model = null;
try {
model = niiService.getModel(number);
}catch(RuntimeException e){
new Exception(e);
}
return model;
}
However, in stead of my custom POJO, I'm seeing the following exception:
{
"timestamp": 1547013989124,
"status": 500,
"error": "Internal Server Error",
"message": "Request processing failed; nested exception is org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure\n\nThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.",
"path": "/model"
}
I was expecting the following JSON structure in stead:
{
"status": 500,
"message": "Server Error"
..
}
Please let me know, what I am missing to get the error response in the way I wanted.
You are missing the throw in front of the custom exception.
Also make sure that you are catching the right exception inside your controller.
Change
catch (RuntimeException e) {
//custom exception
new Exception(e);
}
To
catch (RuntimeException e) {
//custom exception
throw new Exception(e);
}
I missed adding getter and setter to APIError class. Hence, the response was not coming in the way I expected.

Spring Resttemplate exception handling

Below is the code snippet; basically, I am trying to propagate the exception when the error code is anything other than 200.
ResponseEntity<Object> response = restTemplate.exchange(url.toString().replace("{version}", version),
HttpMethod.POST, entity, Object.class);
if(response.getStatusCode().value()!= 200){
logger.debug("Encountered Error while Calling API");
throw new ApplicationException();
}
However in the case of a 500 response from the server I am getting the exception
org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94) ~[spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
Do I really need to wrap the rest template exchange method in try? What would then be the purpose of codes?
You want to create a class that implements ResponseErrorHandler and then use an instance of it to set the error handling of your rest template:
public class MyErrorHandler implements ResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// your error handling here
}
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
...
}
}
[...]
public static void main(String args[]) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new MyErrorHandler());
}
Also, Spring has the class DefaultResponseErrorHandler, which you can extend instead of implementing the interface, in case you only want to override the handleError method.
public class MyErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// your error handling here
}
}
Take a look at its source code to have an idea of how Spring handles HTTP errors.
Spring cleverly treats http error codes as exceptions, and assumes that your exception handling code has the context to handle the error. To get exchange to function as you would expect it, do this:
try {
return restTemplate.exchange(url, httpMethod, httpEntity, String.class);
} catch(HttpStatusCodeException e) {
return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
.body(e.getResponseBodyAsString());
}
This will return all the expected results from the response.
You should catch a HttpStatusCodeException exception:
try {
restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
int statusCode = exception.getStatusCode().value();
...
}
Another solution is the one described here at the end of this post by "enlian":
http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate
try{
restTemplate.exchange(...)
} catch(HttpStatusCodeException e){
String errorpayload = e.getResponseBodyAsString();
//do whatever you want
} catch(RestClientException e){
//no response payload, tell the user sth else
}
Spring abstracts you from the very very very large list of http status code. That is the idea of the exceptions. Take a look into org.springframework.web.client.RestClientException hierarchy:
You have a bunch of classes to map the most common situations when dealing with http responses. The http codes list is really large, you won't want write code to handle each situation. But for example, take a look into the HttpClientErrorException sub-hierarchy. You have a single exception to map any 4xx kind of error. If you need to go deep, then you can. But with just catching HttpClientErrorException, you can handle any situation where bad data was provided to the service.
The DefaultResponseErrorHandler is really simple and solid. If the response status code is not from the family of 2xx, it just returns true for the hasError method.
I have handled this as below:
try {
response = restTemplate.postForEntity(requestUrl, new HttpEntity<>(requestBody, headers), String.class);
} catch (HttpStatusCodeException ex) {
response = new ResponseEntity<String>(ex.getResponseBodyAsString(), ex.getResponseHeaders(), ex.getStatusCode());
}
A very simple solution can be:
try {
requestEntity = RequestEntity
.get(new URI("user String"));
return restTemplate.exchange(requestEntity, String.class);
} catch (RestClientResponseException e) {
return ResponseEntity.status(e.getRawStatusCode()).body(e.getResponseBodyAsString());
}
If you use pooling (http client factory) or load balancing (eureka) mechanism with your RestTemplate, you will not have the luxury of creating a new RestTemplate per class. If you are calling more than one service you cannot use setErrorHandler because if would be globally used for all your requests.
In this case, catching the HttpStatusCodeException seems to be the better option.
The only other option you have is to define multiple RestTemplate instances using the #Qualifier annotation.
Also - but this is my own taste - I like my error handling snuggled tightly to my calls.
The code of exchange is below:
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException
Exception RestClientException has HttpClientErrorException and HttpStatusCodeException exception.
So in RestTemplete there may occure HttpClientErrorException and HttpStatusCodeException exception.
In exception object you can get exact error message using this way: exception.getResponseBodyAsString()
Here is the example code:
public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {
printLog( "Url : " + url);
printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));
try {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);
long start = System.currentTimeMillis();
ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);
printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());
printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));
long elapsedTime = System.currentTimeMillis() - start;
printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");
if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
return responseEntity.getBody();
}
} catch (HttpClientErrorException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}catch (HttpStatusCodeException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}
return null;
}
Here is the code description:
In this method you have to pass request and response class. This method will automatically parse response as requested object.
First of All you have to add message converter.
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
Then you have to add requestHeader.
Here is the code:
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);
Finally, you have to call exchange method:
ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);
For prety printing i used Gson library.
here is the gradle : compile 'com.google.code.gson:gson:2.4'
You can just call the bellow code to get response:
ResponseObject response=new RestExample().callToRestService(HttpMethod.POST,"URL_HERE",new RequestObject(),ResponseObject.class);
Here is the full working code:
import com.google.gson.GsonBuilder;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
public class RestExample {
public RestExample() {
}
public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {
printLog( "Url : " + url);
printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));
try {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);
long start = System.currentTimeMillis();
ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);
printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());
printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));
long elapsedTime = System.currentTimeMillis() - start;
printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");
if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
return responseEntity.getBody();
}
} catch (HttpClientErrorException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}catch (HttpStatusCodeException exception) {
printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
//Handle exception here
}
return null;
}
private void printLog(String message){
System.out.println(message);
}
}
Thanks :)
To extedend #carcaret answer a bit....
Consider your response errors are returned by json message. For example the API may return 204 as status code error and a json message as error list. In this case you need to define which messages should spring consider as error and how to consume them.
As a sample your API may return some thing like this, if error happens:
{ "errorCode":"TSC100" , "errorMessage":"The foo bar error happend" , "requestTime" : "202112827733" .... }
To consume above json and throw a custom exception, you can do as below:
First define a class for mapping error ro object
//just to map the json to object
public class ServiceErrorResponse implements Serializable {
//setter and getters
private Object errorMessage;
private String errorCode;
private String requestTime;
}
Now define the error handler:
public class ServiceResponseErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return (response.getStatusCode().is4xxClientError() ||
response.getStatusCode().is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
#SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<ServiceErrorResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(
ResponseEntity.status(response.getRawStatusCode())
.headers(response.getHeaders())
.body(errorObject)
);
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
The custom Exception will be:
public class ResponseEntityErrorException extends RuntimeException {
private ResponseEntity<ServiceErrorResponse> serviceErrorResponseResponse;
public ResponseEntityErrorException(ResponseEntity<ServiceErrorResponse> serviceErrorResponseResponse) {
this.serviceErrorResponseResponse = serviceErrorResponseResponse;
}
public ResponseEntity<ServiceErrorResponse> getServiceErrorResponseResponse() {
return serviceErrorResponseResponse;
}
}
To use it:
RestTemplateResponseErrorHandler errorHandler = new
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
This is how to handle exceptions in Rest Template
try {
return restTemplate.exchange("URL", HttpMethod.POST, entity, String.class);
}
catch (HttpStatusCodeException e)
{
return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
.body(e.getResponseBodyAsString());
}
Here is my POST method with HTTPS which returns a response body for any type of bad responses.
public String postHTTPSRequest(String url,String requestJson)
{
//SSL Context
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
//Initiate REST Template
RestTemplate restTemplate = new RestTemplate(requestFactory);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//Send the Request and get the response.
HttpEntity<String> entity = new HttpEntity<String>(requestJson,headers);
ResponseEntity<String> response;
String stringResponse = "";
try {
response = restTemplate.postForEntity(url, entity, String.class);
stringResponse = response.getBody();
}
catch (HttpClientErrorException e)
{
stringResponse = e.getResponseBodyAsString();
}
return stringResponse;
}
I fixed it by overriding the hasError method from DefaultResponseErrorHandler class:
public class BadRequestSafeRestTemplateErrorHandler extends DefaultResponseErrorHandler
{
#Override
protected boolean hasError(HttpStatus statusCode)
{
if(statusCode == HttpStatus.BAD_REQUEST)
{
return false;
}
return statusCode.isError();
}
}
And you need to set this handler for restemplate bean:
#Bean
protected RestTemplate restTemplate(RestTemplateBuilder builder)
{
return builder.errorHandler(new BadRequestSafeRestTemplateErrorHandler()).build();
}
Read about global exception handling in global exception handler add the below method. this will work.
#ExceptionHandler( {HttpClientErrorException.class, HttpStatusCodeException.class, HttpServerErrorException.class})
#ResponseBody
public ResponseEntity<Object> httpClientErrorException(HttpStatusCodeException e) throws IOException {
BodyBuilder bodyBuilder = ResponseEntity.status(e.getRawStatusCode()).header("X-Backend-Status", String.valueOf(e.getRawStatusCode()));
if (e.getResponseHeaders().getContentType() != null) {
bodyBuilder.contentType(e.getResponseHeaders().getContentType());
}
return bodyBuilder.body(e.getResponseBodyAsString());
}
There is also an option to use TestRestTemplate. It is very useful for integration and E2E tests, when you need to validate all status codes manually (for example in negative test-cases).
TestRestTemplate is fault-tolerant. This means that 4xx and 5xx do not result in an exception being thrown and can instead be detected via the response entity and its status code.
Try using #ControllerAdvice. This allows you to handle the exception only once and have all 'custom' handled exceptions in one place.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
example
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler(MyException.class)
protected ResponseEntity<Object> handleMyException(){
MyException exception,
WebRequest webRequest) {
return handleExceptionInternal(
exception,
exception.getMessage(),
exception.getResponseHeaders(),
exception.getStatusCode(),
webRequest);
}

Ribbon LB / OAuth2RestTemplate

I'm trying to invoke the load balancer to get access to a service instance in order to build the OAuth2RestTemplate access token uri within my configuration class, but for some reason I keep getting exceptions when the bean is instantiated. Has anyone come across this issue before? Or perhaps may have some insight?
Factory method 'restTemplate' threw exception; nested exception is java.lang.IllegalStateException: Unable to locate ILoadBalancer for service: service
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'restTemplate' defined in class path resource [com/apop/services/config/rest/RestConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.client.OAuth2RestTemplate]: Factory method 'serviceRestTemplate' threw exception; nested exception is java.lang.IllegalStateException: Unable to locate ILoadBalancer for service: service
Also here are my service and configuration classes:
#Service
public class ServiceIntegration implements Service, Serializable {
#Resource(name = "serviceRestTemplate")
OAuth2RestTemplate serviceRestTemplate;
#Autowired
private LoadBalancerClient balancerClient;
#Value("${service}")
private String service;
private ResponseExceptionHelper responseExceptionHelper = new ResponseExceptionHelper();
public ResponseExceptionHelper getResponseExceptionHelper() {
return responseExceptionHelper;
}
public void setResponseExceptionHelper(
ResponseExceptionHelper responseExceptionHelper) {
this.responseExceptionHelper = responseExceptionHelper;
}
/**
* #param payload
* #param path
* #param method
* #return
* #throws ResponseException
*/
private String execute(OAuth2RestTemplate restTemplate, String endPoint, String payload, HttpMethod method)
throws ResponseException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<String>(payload, headers);
String response = null;
try {
ServiceInstance serviceInstance = balancerClient.choose(service);
String uri = String.format("http://%s:%s/%s", serviceInstance.getHost(), serviceInstance.getPort(), service) + endPoint;
ResponseEntity<String> responseEntity = restTemplate.exchange(uri, method, requestEntity, String.class);
if (responseEntity != null) {
response = responseEntity.getBody();
}
} catch (Exception e) {
logger.error(error, e);
throw e;
}
logger.info(response);
return response;
}
}
Config:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class RestConfiguration {
#Value("${service}")
private String service;
#Value("${token.service.endpoint}")
private String tokenServiceEndpoint;
#Value("${clientId}")
private String clientId;
#Value("${clientSecret}")
private String clientSecret;
#Value("${grant}")
private String grant;
#Autowired
private LoadBalancerClient balancerClient;
#Bean
public OAuth2RestTemplate serviceRestTemplate() {
ClientCredentialsResourceDetails resources = getClientDetails();
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resources, new DefaultOAuth2ClientContext());
return restTemplate;
}
public ClientCredentialsResourceDetails getClientDetails() {
ServiceInstance serviceInstance = balancerClient.choose(service);
String uri = String.format("http://%s:%s/%s", serviceInstance.getHost(), serviceInstance.getPort(), legacyService) + tokenServiceEndpoint;
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(uri);
resource.setClientSecret(clientId);
resource.setClientId(clientSecret);
resource.setGrantType(grant);
return resource;
}
}
You are trying to do service discovery in #Bean definition (so super early) before the service catalog is available. It would be better to use a RibbonInterceptor in an otherwise normal OAuth2RestTemplate (like in the RibbonAutoConfiguration).

Resources