I am playing around Spring Cloud Hystrix and I got this weird error that my Fallback method is not being invoked. My Controller is below .
#Controller
public class DashboardController {
#LoadBalanced
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder){
return builder.build();
}
#Autowired
private RestTemplate restTemplate;
#HystrixCommand(fallbackMethod = "getFareBackup")
#RequestMapping("/dashboard")
public String getFareDashboard(Model m) {
try {
ResponseEntity<List<BusFare>> responseEntity = restTemplate.exchange("http://busfare-service/api/v1/fare/",
HttpMethod.GET, null, new ParameterizedTypeReference<List<BusFare>>() {
});
m.addAttribute("fareList", responseEntity.getBody());
} catch (Exception e) {
e.printStackTrace();
}
return "dashboard";
}
public String getFareBackup(Model m){
System.out.println("Fallback operation called");
m.addAttribute("fareList", new ArrayList<BusFare>().add(new BusFare(1, BigDecimal.valueOf(0.7), "Regular")));
return "dashboard";
}
}
As you can see, I set the fallbackMethod properly, however, when I run the server and point my browser to the end point, I get an exception saying that my server is down, as I understand when my service is down it should invoke the fallbackMethod, but it in my case that is not the case, my fallbackMethod is basically not being invoked.
java.lang.IllegalStateException: No instances available for busfare-service
I am missing something in my code?
It seems my like, Hystrix handles this fallbackMethod thru errorHandling. What messed up my code that caused my fallback not being invoked is the errorHandling.
#HystrixCommand(fallbackMethod = "getFareBackup")
#RequestMapping("/dashboard")
public String getFareDashboard(Model m) {
ResponseEntity<List<BusFare>> responseEntity = restTemplate.exchange("http://busfare-service/api/v1/fare/",
HttpMethod.GET, null, new ParameterizedTypeReference<List<BusFare>>() {
});
m.addAttribute("fareList", responseEntity.getBody());
return "dashboard";
}
With the code above, the fallbackMethod is now working.
Related
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
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 have created a Spring Restful Service and Spring MVC application.
Restful Service ::
Restful service returns an entity if its existing in DB. If it doesn't exist It returns a custom Exception information in ResponseEntity object.
It is working as expected tested using Postman.
#GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(#PathVariable Long itemId, #RequestHeader HttpHeaders httpHeaders) {
MyItem myItem = myitemService.validateMyItem(itemId);
ResponseEntity<MyItem> responseEntity = null;
if (myItem == null) {
throw new ItemNotFoundException("Item Not Found!!!!");
}
responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
return responseEntity;
}
If the requested Entity does not exist Restful Service returns below.
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
But when I am calling the above service from a spring MVC application using RestTemplate, It is returning a valid object if it exists.
If the requested object does not exist Restful service is returning the exception information but its not reaching the calling(spring MVC) application.
Spring MVC application calls Restful Web Service using Rest template
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
This is expected behavior. Rest template throws exception when the http status is client error or server error and returns the response when http status is not error status.
You have to provide implementation to use your error handler, map the response to response entity and throw the exception.
Create new error exception class with ResponseEntity field.
public class ResponseEntityErrorException extends RuntimeException {
private ResponseEntity<ErrorResponse> errorResponse;
public ResponseEntityErrorException(ResponseEntity<ErrorResponse> errorResponse) {
this.errorResponse = errorResponse;
}
public ResponseEntity<ErrorResponse> getErrorResponse() {
return errorResponse;
}
}
Custom error handler which maps the error response back to ResponseEntity.
public class ResponseEntityErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
protected boolean hasError(HttpStatus statusCode) {
return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpMessageConverterExtractor<ExceptionResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ExceptionResponse.class, messageConverters);
ExceptionResponse 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;
}
}
RestTemplate Configuration - You have to set RestTemplate's errorHandler to ResponseEntityErrorHandler.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntityErrorHandler errorHandler = new ResponseEntityErrorHandler();
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}
Calling Method
#Autowired restTemplate
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
try {
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
} catch (ResponseEntityErrorException re) {
ResponseEntity<ErrorResponse> errorResponse = re.getErrorResponse();
}
Try using the #ResponseBody annotation on your Exceptionhandler. e.g:
public #ResponseBody ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {... }
You should use Custom Exception Handler to fix your case. It looks like this
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public CustomResponseEntityExceptionHandler() {
super();
}
// 404
#ExceptionHandler(value = { EntityNotFoundException.class, ResourceNotFoundException.class })
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
BaseResponse responseError = new BaseResponse(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND.name(),
Constants.HttpStatusMsg.ERROR_NOT_FOUND);
logger.error(ex.getMessage());
return handleExceptionInternal(ex, responseError, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
}
And your code should throw some exception, eg:
if (your_entity == null) {
throw new EntityNotFoundException("said something");
}
If you get this case in somewhere else again, you just throw exception like above. Your handler will take care the rest stuffs.
Hope this help.
I've started your application and works just fine.
Maven :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
The controller class is :
#Controller
public class ValidationController {
#GetMapping(value = "/validate/{itemId}")
public #ResponseBody ResponseEntity<MyItem> validateItem(#PathVariable Long itemId) {
if (itemId.equals(Long.valueOf(1))) {
throw new ItemNotFoundException();
}
return new ResponseEntity<>(new MyItem(), HttpStatus.OK);
}
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
}
and the test:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ValidationController.class, secure = false)
public class TestValidationController {
#Autowired
private MockMvc mockMvc;
#Test
public void testExpectNotFound() throws Exception {
mockMvc.perform(get("/validate/1"))
.andExpect(status().isNotFound());
}
#Test
public void testExpectFound() throws Exception {
mockMvc.perform(get("/validate/2"))
.andExpect(status().isOk());
}
}
Are you sure the url you are trying to use with RestTemplate is correct?
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
Your get method is #GetMapping(value = "/validate/{itemId}"
If you don't have request mapping at the level of the controller the url should be:
http://localhost:8080/validate/1
Another difference is the missing #ResponseBody on your controller method.
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 controller like this:
#PostMapping("/rest_upload1")
public ResponseEntity upload1(#RequestParam("file") MultipartFile multipartFile) throws IOException {
throw new IllegalArgumentException();
}
and in configuration I have settings:
spring.http.multipart.max-file-size=100MB
spring.http.multipart.max-request-size=100MB
it means that spring will throw MultipartException in case if file exceeds 100MB.
To handle this exception I wrote handler:
#ControllerAdvice
public class RestExceptionHandlerAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(MultipartException.class)
#ResponseBody
public ResponseEntity<ApiError> handleException(MultipartException e) {
logger.warn("MultipartException:", e);
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST,
String.valueOf(HttpStatus.BAD_REQUEST),
ExceptionUtils.getRootCauseMessage(e),
Collections.emptyList());
return new ResponseEntity<ApiError>(apiError, HttpStatus.BAD_REQUEST);
}
In case of error this code invokes(i see it in debug)
but in browser I don't see response:
I googled a lot of time and looked loke everuthing ok. And I tried to add handler for IllegalArgumentException:
#ExceptionHandler(IllegalArgumentException.class)
#ResponseBody
public ResponseEntity<ApiError> handleException(IllegalArgumentException e) {
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST,
String.valueOf(HttpStatus.BAD_REQUEST),
ExceptionUtils.getRootCauseMessage(e),
Collections.emptyList());
return new ResponseEntity<ApiError>(apiError, HttpStatus.BAD_REQUEST);
}
And I upload file less than 100mb. At this case result differs:
But anyway response code is wrong.
What can be wrong?
P.S.
I tried:
#ExceptionHandler(MultipartException.class)
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
#ResponseBody
public String handleException(MultipartException e) {
return ExceptionUtils.getRootCauseMessage(e);
}
it looks like the same as here:
How to handle maximum file size Exception in Spring Boot?
P.S.2
I found workaround but it looks like bug in sring:
I added dependencies:
compile "org.apache.commons:commons-io:1.3.2"
compile "commons-fileupload:commons-fileupload:1.3.3"
register beans:
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(10);
return multipartResolver;
}
and wrote:
#ControllerAdvice
public class UploadExceptionHandler {
#ExceptionHandler(MaxUploadSizeExceededException.class)
#ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
#ResponseBody
public String handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
return e.getMessage();
}
}
Not very clever tomcat developers added new feature which we need to disable/override.
It is working after I have added:
#Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbedded() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
// connector other settings...
// configure maxSwallowSize
if ((connector.getProtocolHandler() instanceof AbstractHttp11Protocol<?>)) {
// -1 means unlimited, accept bytes
((AbstractHttp11Protocol<?>) connector.getProtocolHandler()).setMaxSwallowSize(-1);
}
});
return tomcat;
}