I am using below code to call remote API to remove user id(http://localhost:8080/remove).
try {
final RestTemplate restTemplate = new RestTemplate();
final UriComponentsBuilder builder =
UriComponentsBuilder.fromUriString(url);
builder.queryParam("acdc_id", acdcId);
ResponseEntity<ServiceResponse> result =
restTemplate.exchange(
builder.toUriString(),
HttpMethod.DELETE,
null,
ServiceResponse.class);
}catch(Exception e){
//exception handling
}
Remote API return 200 http code for success flow(working fine), but when some user id will not available then API sent below custom response:
{
"error code": "404",
"error": "USER ID Node not found : xyz"
}
I have already ServiceResponse.java class to get above response, but Rest Template returning below error in this case.
org.springframework.web.client.HttpClientErrorException$NotFound: 404 null
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:85)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:122)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:102)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:778)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:736)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:579)
My ServiceResponse class is,
#JsonIgnoreProperties(ignoreUnknown = true)
public class ServiceResponse {
#JsonProperty(value = "error code")
private String errorCode;
#JsonProperty(value = "error")
private String error;
/**
* #return the error.
*/
public String getError() {
return error;
}
/**
* #param error The error to set.
*/
public void setError(final String error) {
this.error = error;
}
/**
* #return the errorCode.
*/
public String getErrorCode() {
return errorCode;
}
/**
* #param errorCode The errorCode to set.
*/
public void setErrorCode(final String errorCode) {
this.errorCode = errorCode;
}
}
Could you please help me here to fix my issue, or provide any suggestion, how I can get proper response from API instead null error
As I mentioned in the comment, you're getting HttpClientErrorException, which should be caught and dealt with. You're catching the whole Exception class, but there is no code in it. Or you can use #ControllerAdvice and #ExceptionHandler together to achieve this as well.
You can use Controller Advice #ControllerAdvice annotation to handle the exceptions . You can send Custom response from the method. You can define exceptionHandler for HttpClientErrorException and send custom response from this method.
Please check https://www.tutorialspoint.com/spring_boot/spring_boot_exception_handling.htm for further details.
One more option is you can use CustomResponseErrorHandler something like below
#Component
public class RestTemplateResponseErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (
httpResponse.getStatusCode().series() == CLIENT_ERROR
|| httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getStatusCode()
.series() == HttpStatus.Series.SERVER_ERROR) {
// handle SERVER_ERROR
} else if (httpResponse.getStatusCode()
.series() == HttpStatus.Series.CLIENT_ERROR) {
// handle CLIENT_ERROR
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}
And then use it like
#Bean
RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
return restTemplate;
}
Please check https://www.baeldung.com/spring-rest-template-error-handling for ResponseErrorHandler
Related
I have an rest web service. If any exception thrown, web service return http 500 error. But I don't want to send this error response with exception stack trace. I just want to send with error code and error message. I didn't achieve this. How can I do this?
I already tried #ControllerAdvice and #ExceptionHandler annotations but I couldn't. When I used #ResponseStatus annotation, always send static "reason" value. How can I set this value? Thank for your help.
public class SendMessageController{
private Logger log = LogManager.getLogger(getClass());
#Autowired
private QueueService queueService;
#RequestMapping(value="/message/check", method = RequestMethod.POST, headers={ "content-type=application/json"})
public #ResponseBody
ApiResponse sendMessage(#RequestBody String requestMessage) throws Exception {
try {
return new ApiResponse(queueService.processRequestForJSONString(requestMessage);
} catch (Exception e) {
throw new GenericException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
//throw e;
}
}
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR, reason="Exception Message")
public class GenericException extends Exception {
public HttpStatus httpCode;
public String errorMessage;
public GenericException(HttpStatus httpCode, String errorMessage){
this.httpCode = httpCode;
this.errorMessage = errorMessage;
//I can't set "reason"
}
}
}
There are many possible solutions and I'm pretty sure an ErrorHandler is a much better way to go.
#GetMapping(value="/{empId}", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<EmployeeInfoItem> getEmployeeInfo(#PathVariable("empId") Integer empId) {
try {
...
} catch (Exception e) {
logger.error( e.getMessage() );
return ResponseEntity.status(HttpStatus.FAILED_DEPENDENCY).build();
}
}
I want to use this code to receive http link with values:
#PostMapping(value = "/v1/notification")
public String handleNotifications(#RequestParam("notification") String itemid) {
// parse here the values
return "result successful result";
}
How I can return http code 200 - successful response?
And also for example if there is a code exception into code processing how can I return error 404?
If you are using spring:
#PostMapping(value = "/v1/notification")
public ResponseEntity handleNotifications(#RequestParam("notification") String itemid) {
// parse here the values
return ResponseEntity.ok().build();
//OR ResponseEntity.ok("body goes here");
}
If you use #RestController it should return 200 by default.
But anyway, you can set a particular response status by #ResponseStatus annotation (even if the methods returns void) or you can return a custom response by ResponseEntity.
EDIT: added error handling
For error handling, you can return a particular response entity:
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body("some body ");
or you can use #ExceptionHandler:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public void handleError(Exception ex) {
// TODO: log exception
}
You can do it by annotating your method with #ResponseStatus using HttpStatus.OK (However it should be 200 by default), like this:
Some controller
#PostMapping(value = "/v1/notification")
#ResponseStatus(HttpStatus.OK)
public String handleNotifications(#RequestParam("notification") String itemid) throws MyException {
if(someCondition) {
throw new MyException("some message");
}
// parse here the values
return "result successful result";
}
Now, in order to return a custom code when handling a specific exception you can create a whole separate controller for doing this (you can do it in the same controller, though) which extends from ResponseEntityExceptionHandler and is annotated with #RestControllerAdvice and it must have a method for handling that specific exception as shown below:
Exception handling controller
#RestControllerAdvice
public class ExceptionHandlerController extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
protected ResponseEntity<Object> handleMyException(MyException ex, WebRequest req) {
Object resBody = "some message";
return handleExceptionInternal(ex, resBody, new HttpHeaders(), HttpStatus.NOT_FOUND, req);
}
}
You can do something like this:
#PostMapping(value = "/v1/notification")
public ResponseEntity<String> handleNotifications(
#RequestParam("notification") String itemid) {
// parse here the values
return new ResponseEntity<>("result successful result",
HttpStatus.OK);
}
I am testing an #RestController which has an API endpoint such as /api/dataobject. If the object (in JSON format) that is posted to this endpoint is missing some part of its meta data, the API should respond with a Http status of bad request (400).
When testing it through Postman, this works, however in my unit test where the controller is mocked it still returns a status 200.
The method in the RestController:
#RequestMapping("/api/dataobject")
public ResponseEntity postDataObject(#RequestBody final DataObject dataObject) throws InvalidObjectException {
if (!dataObjectValidator.validateDataObject(dataObject)) {
throw new InvalidObjectException("Data object was invalid: " + dataObject.toString());
}
return new ResponseEntity(HttpStatus.OK);
}
The InvalidObjectException is caught by a class annotated with #ControllerAdvice which extends ResponseEntityExceptionHandler and is handled as follows:
#ExceptionHandler(value = InvalidObjectException.class)
protected ResponseEntity<Object> handleInvalidObject(final InvalidObjectException exception, final WebRequest request) {
final String bodyOfResponse = exception.getMessage();
return handleExceptionInternal(exception, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Now, the unit test class is as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(DataObjectController.class)
public class DataObjectControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private DataObjectController dataObjectController;
private final String uri = "/api/idataobject";
#Test
public void noAppName() throws Exception {
DataObject object = getDataObjectNoAppName();
final String body = new Gson().toJson(object);
given(dataObjectController.postDataObject(object)).willReturn(new ResponseEntity(HttpStatus.BAD_REQUEST));
mvc.perform(post(uri)
.content(body)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
Even though the object is invalid, and I've said that the given object would return a HttpStatus 400, I get a 200 status in return.
Clearly I'm missing something here, but what?
I have a REST api that responds with some additional non JSON data in the body content. This breaks the use of RestTemplate and jackson. Can I intercept the http response body prior to the parsing?
I am using RestTemplate.getForObject.
I've taken a look at the RestTemplate and couldn't see an appropriate method.
You can try to implement ClientHttpRequestInterceptor and assign it to restTemplate. Implement intercept method:
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
ClientHttpResponse response=clientHttpRequestExecution.execute(httpRequest, bytes);
//...do magic with response body from getBody method
return response;
}
You might have to extend AbstractClientHttpResponse with your own implementation to do that.
Another option could be to treat the response from the REST API as String, then format the string as needed and explicitly map it to object using ObjectMapper.
Then in your restTemplate you would have:
String result = restTemplate.getForObject(url, String.class, host);
//..trim the extra stuff
MyClass object=objectMapper.readValue(result, MyClass.class);
Yet another option would be to implement your own HttpMessageConverter which extends AbstractJackson2HttpMessageConverter and register it with restTemplate. In my opinion that would be the cleaneast from the Spring point of view
Another way would be to unwrap the response by implementing a ClientHttpRequestInterceptor along with a ClientHttpResponse.
#Component
public class MyInterceptor implements ClientHttpRequestInterceptor {
#Autowired
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory;
#Autowired
MyRequestAdvice requestAdvice;
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
byte[] wrappedBody = requestAdvice.wrapRequest(bytes);
ClientHttpResponse res = clientHttpRequestExecution.execute(httpRequest, wrappedBody);
return responseWrapperBeanFactory.apply(res);
}
}
Here's the bean config for the MyResponseWrapper:
#Bean
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory() {
return this::getMyResponseWrapper;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyResponseWrapper getMyResponseWrapper(ClientHttpResponse originalResponse) {
return new MyResponseWrapper(originalResponse);
}
#Bean
public RestTemplate restTemplate(#Autowired MyInterceptor interceptor) {
RestTemplate t = new RestTemplate();
t.setInterceptors(Arrays.asList(interceptor));
// other setup code
return t;
}
And here's the ClientHttpResponse implementation:
public class MyResponseWrapper implements ClientHttpResponse {
private byte[] filteredContent;
private ByteArrayInputStream responseInputStream;
private ClientHttpResponse originalResponse;
public MyResponseWrapper(ClientHttpResponse originalResponse) {
this.originalResponse = originalResponse;
try {
filteredContent = MyContentUnwrapper.unwrapResponse(originalResponse.getBody().readAllBytes());
} catch (Exception e) {
throw new RuntimeException("There was a problem reading/decoding the response coming from the service ", e);
}
}
#Override
public HttpStatus getStatusCode() throws IOException {
return originalResponse.getStatusCode();
}
#Override
public int getRawStatusCode() throws IOException {
return originalResponse.getRawStatusCode();
}
#Override
public String getStatusText() throws IOException {
return originalResponse.getStatusText();
}
#Override
public void close() {
if (responseInputStream != null) {
try {
responseInputStream.close();
} catch (IOException e) { /* so long */}
}
}
#Override
public InputStream getBody() throws IOException {
if (responseInputStream == null) {
responseInputStream = new ByteArrayInputStream(filteredContent);
}
return responseInputStream;
}
#Override
public HttpHeaders getHeaders() {
return originalResponse.getHeaders();
}
}
From your Controller you can try to return a ResponseEntity and manipulate the entity object manually
If you don't need these extra properties you may add:
#JsonIgnoreProperties(ignoreUnknown = true)
to your mapping class.
From docs:
Property that defines whether it is ok to just ignore any unrecognized
properties during deserialization. If true, all properties that are
unrecognized -- that is, there are no setters or creators that accept them
-- are ignored without warnings (although handlers for unknown properties,
if any, will still be called) without exception.
Does not have any effect on serialization.
I have a method in controller with has parameter for example
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader("ETag") int etag)
If there is no ETag header in request - client gets 400 (BAD_REQUEST), which is not any informative.
I need to somehow handle this exception and send my own exception to client (I use JSON for this purpose).
I know that I can intercept exception via #ExceptionHandler, but in that case all HTTP 400 requests will be handled, but I want that have missing ETag in headers.
Any ideas?
You can also achieve this by use of annotation #ControllerAdvice from spring.
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{
/**
* Handle ServletRequestBindingException. Triggered when a 'required' request
* header parameter is missing.
*
* #param ex ServletRequestBindingException
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ResponseEntity object
*/
#Override
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), headers, status);
}
}
The response when you access your API without the required request header is:
Missing request header 'Authorization' for method parameter of type String
Like this exception, you can customise all other exceptions.
In case Spring version is 5+ then the exact exception you need to handle is the MissingRequestHeaderException. If your global exception handler class extends ResponseEntityExceptionHandler then adding an #ExceptionHandler for ServletRequestBindingException won't work because MissingRequestHeaderException extends ServletRequestBindingException and the latter is handled inside the handleException method of the ResponseEntityExceptionHandler. If you try you're going to get Ambiguous #ExceptionHandler method mapped for ... exception.
There are two ways to achieve what you are trying
First using #RequestHeader with required false
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader(value="ETag", required=false) String ETag) {
if(ETag == null) {
// Your JSON Error Handling
} else {
// Your Processing
}
}
Second using HttpServletRequest instead of #RequestHeader
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(HttpServletRequest request) {
String ETag = request.getHeader("ETag");
if(ETag == null) {
// Your JSON Error Handling
} else {
// Your Processing
}
}
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of missing header
For example :
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
In Spring 5+ it is as simple as this. ErrorResponse is your own object to return
#RestControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<ErrorResponse> handleException(MissingRequestHeaderException ex) {
log.error("Error due to: " + ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse();
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
You should user an #ExceptionHandler method that looks if ETag header is present and takes appropriate action :
#ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
public onErr400(#RequestHeader(value="ETag", required=false) String ETag,
UnsatisfiedServletRequestParameterException ex) {
if(ETag == null) {
// Ok the problem was ETag Header : give your informational message
} else {
// It is another error 400 : simply say request is incorrect or use ex
}
}
If you don't want to handle this in your request mapping, then you could create a Servlet Filter and look for the ETag header in the Filter. If it's not there, then throw the exception. This would apply to only requests that match your filter's URL mapping.
public final class MyEtagFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String etag = request.getHeader("ETag");
if(etag == null)
throw new MissingEtagHeaderException("...");
filterChain.doFilter(request, response);
}
}
You'll have to implement your own MissingEtagHeaderException, or use some other existing exception.
This is relatively simple. Declare two handler methods, one that declares the appropriate header in the #RequestMapping headers attribute and one that doesn't. Spring will take care to invoke the appropriate one based on the content of the request.
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST, headers = "ETag")
#ResponseStatus(HttpStatus.CREATED)
public void postWith(#RequestHeader("ETag") int etag) {
// has it
}
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void postWithout() {
// no dice
// custom failure response
}
You can also intercept the exception without extending ResponseEntityExceptionHandler:
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(ServletRequestBindingException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex) {
// return a ResponseEntity<Object> object here.
}
}
You can add #Nullable to this request param, and in case of absence, request still enters the controller without throwing MissingRequestHeaderException, and you add manual validation to throw whatever you like in controller and handle in the ExceptionHandler.
You can create a custom exception class e.g. InvalidRequestHeaderException.java. You can customise your exception message here.
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidRequestHeaderException extends RuntimeException {
public InvalidRequestHeaderException() {
super("Invalid request header provided.");
}
}
In your controller, you can throw an exception if the header provided is invalid.
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader("ETag") int etag) {
// some code
if (!isSupportedPlatform(platform)) {
throw new InvalidRequestHeaderException();
}
// some code
}
You can then create a ValidationHandler.java to handle these exceptions.
#RestControllerAdvice
public class ValidationHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
MissingRequestHeaderException.class,
InvalidRequestHeaderException.class
})
protected ResponseEntity<Object> handleRequestHeaderException(Exception ex) {
log.error(ex.getMessage());
return ResponseEntity.badRequest().body(ErrorResponse.builder()
.status(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.reason(ex.getMessage()).build());
}
#AllArgsConstructor
#Getter
#Builder
public static class ErrorResponse {
private String status;
private String reason;
}
}
By using MissingRequestHeaderException, it will throw an exception if what you've annotated with #RequestHeader is missing, so you will get an exception like this:
Missing request header 'Etag' for method parameter of type int
And when the request header is present but not valid this exception will be thrown:
Invalid request header provided.