Spring #ExceptionHandler returns wrong HttpStatus code - java

TL;DR: #ExceptionHandler function is returning 200 OK instead of 400 Bad Request for a MissingServletParameterException when calling HttpServletResponse.getStatus & HttpStatus.valueOf(HttpServletResponse.getStatus)).name(). MissingServletParameterException is only used as an example, also happens for other exceptions too.
Hello,
The issue I'm having is that I'm trying to integrate Raygun (a crash reporting system) with our Java/Spring Boot application. The easiest way I've found was to create a custom exception handler that would display the error message to the user as well as pass the exception to Raygun.
Originally, I tried the implementation suggested here with my own Raygun implementation added https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
private static ApiAccessToken accessToken = new ApiAccessToken();
private static String databaseName = null;
#ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
// Display the error message to the user, and send the exception to Raygun along with any user details provided.
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
ArrayList tags = new ArrayList<String>();
tags.add("username: " + accessToken.getUsername());
tags.add("database: " + accessToken.getDatabaseName());
client.Send(e, tags);
accessToken = null;
return mav;
} else if (databaseName != null) {
ArrayList tags = new ArrayList<String>();
tags.add("database: " + databaseName);
client.Send(e, tags);
databaseName = null;
return mav;
} else {
client.Send(e);
return mav;
}
}
The problem I encountered with this is that we have both public and private API endpoints. The private API endpoints are used for our iOS applications, whereas the public API endpoints have no front-end. They were designed for businesses to be able to integrate into their own systems (PowerBI, Postman, custom integrations, etc). And so there is no views that I can redirect to using ModelAndView.
Instead, what I've decided to do is instead of using ModelAndView, I'm just returning a string that has been formatted to mimic Spring's default JSON error message.
#ExceptionHandler(value = Exception.class)
public #ResponseBody String defaultErrorHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) throws Exception {
// Create a customised error message that imitates the Spring default Json error message
StringBuilder sb = new StringBuilder("{ \n")
.append(" \"timestamp\": ").append("\"").append(DateTime.now().toString()).append("\" \n")
.append(" \"status\": ").append(resp.getStatus()).append(" \n")
.append(" \"error\": ").append("\"").append(HttpStatus.valueOf(resp.getStatus()).name()).append("\" \n")
.append(" \"exception\": ").append("\"").append(e.getClass().toString().substring(6)).append("\" \n")
.append(" \"message\": ").append("\"").append(e.getMessage()).append("\" \n")
.append(" \"path\": ").append("\"").append(req.getServletPath()).append("\" \n")
.append("}");
String errorMessage = String.format(sb.toString());
// Display the error message to the user, and send the exception to Raygun along with any user details provided.
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
ArrayList tags = new ArrayList<String>();
tags.add("username: " + accessToken.getUsername());
tags.add("database: " + accessToken.getDatabaseName());
client.Send(e, tags);
accessToken = null;
return errorMessage;
} else if (databaseName != null) {
ArrayList tags = new ArrayList<String>();
tags.add("database: " + databaseName);
client.Send(e, tags);
databaseName = null;
return errorMessage;
} else {
client.Send(e);
return errorMessage;
}
}
The only issue with this is that when I purposefully cause an exception to be thrown, it returns with a HTTP status of 200 OK which is obviously not correct.
For instance, this is with defaultErrorHandler() commented out (sends nothing to Raygun):
{
"timestamp": "2017-07-18T02:59:45.131+0000",
"status": 400,
"error": "Bad Request",
"exception":
"org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter ‘foo’ is not present",
"path": "/api/foo/bar/v1"
}
And this is with it not commented out (sends the exception to Raygun):
{
"timestamp": "2017-07-25T06:21:53.895Z"
"status": 200
"error": "OK"
"exception": "org.springframework.web.bind.MissingServletRequestParameterException"
"message": "Required String parameter 'foo' is not present"
"path": "/api/foo/bar/V1"
}
Any help or advice on what I'm doing wrong would be greatly appreciated. Thank you for your time.

In your controller advice try this way to map exception type to Http-Status as follows:
if (ex instanceof MyException)
{//just an example.
return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST);
}
else
{//all other unhandled exceptions
return new ResponseEntity<>(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
Here MyException is the type of exception you are throwing at runtime. Say I am handling Bad request.

I'm still unsure why it was returning a 200 OK status when an exception was being thrown. But I've realised that what I was doing with trying to create a string that mimics Spring's default json error message, was overly complex and not necessary at all.
Once I had sent the exception through to Raygun, I can just rethrow the exception and let the framework handle it like any exception annotated with #ResponseStatus.
#ExceptionHandler(value = Exception.class)
public void defaultErrorHandler(Exception e) throws Exception {
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise send the exception Raygun and then rethrow it and let the framework handle it
if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
ArrayList tags = new ArrayList<String>();
tags.add("username: " + accessToken.getUsername());
tags.add("database: " + accessToken.getDatabaseName());
client.Send(e, tags);
accessToken = null;
throw e;
} else if (databaseName != null) {
ArrayList tags = new ArrayList<String>();
tags.add("database: " + databaseName);
client.Send(e, tags);
databaseName = null;
throw e;
} else {
client.Send(e);
throw e;
}
}
A bare bones implentation would look like this:
#ExceptionHandler(value = Exception.class)
public void defaultErrorHandler(Exception e) throws Exception {
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
// If the exception is annotated with #ResponseStatus rethrow it and let the framework handle it
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise send the exception Raygun and then rethrow it and let the framework handle it
else {
client.Send(e);
throw e;
}
}

Related

Mockito Test Case for Custom Exception

I am testing return values in a method but need to also test exceptions.
Below is a code snippet of one of the exceptions - how should I test this ?
#Override
public Response generateResponse(Request request) throws CustomException {
try {
GenerateResponse response = client.generateResponse(headers, generateRequest);
return response;
} catch (FeignException.BadRequest badRequest) {
String message = "Received Bad Request";
throw new CustomException(message, "" + badRequest.status());
} catch (FeignException.Unauthorized unauthorized) {
log.error(unauthorized.contentUTF8());
String message = "Received UnAuthorized Exception ";
throw new CustomException(message, "" + unauthorized.status());
}
}}
I have tested the happy path for the method I am testing using the following:
Mockito.when(service.getResponse(Mockito.any(), Mockito.any())).thenReturn(getResponse);
Mockito.when(service.getResponse(Mockito.any(), Mockito.any())).thenThrow(new CustomException());
If you want the mock to throw an error, you do not want thenReturn but thenThrow

Why handleResponse(URI url, HttpMethod method, ClientHttpResponse response) method of RestTemplate.class is getting called for a 200 Succeess response

I am calling an external API from my code using RestTemplate like below:
try {
responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity,
UploadResonse.class);
} catch (BusinessException ex) {
fetchErrorResponseEntity = ex.getResponseEntity();
if (fetchErrorResponseEntity.getStatusCodeValue() == 404) {
throw new BusinessException(ex.getMessage(), ErrorResponse.NOT_FOUND);
} else if (fetchErrorResponseEntity.getStatusCodeValue() == 500) {
throw new BusinessException(ex.getMessage(),
ErrorResponse.INTERNAL_SERVER_ERROR);
} else if (fetchErrorResponseEntity.getStatusCodeValue() == 400) {
throw new BusinessException(ex.getMessage(), ErrorResponse.INVALID_REQUEST);
}
}
This API call is returning 200 Success but when I debug it, it still goes to handleResponse(URI url, HttpMethod method, ClientHttpResponse response) method of RestTemplate.class
And then it's coming to my RestTemplateErrorHandler.java file
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return clientHttpResponse.getStatusCode() != HttpStatus.OK;
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse)
throws IOException {
String errMessage = getErrMessage(clientHttpResponse);
HttpStatus status = clientHttpResponse.getStatusCode();
switch (status) {
case BAD_REQUEST: // 400
throw new BusinessException(errMessage,
ErrorResponse.INVALID_REQUEST);
case NOT_FOUND:
throw new BusinessException(errMessage, ErrorResponse.NOT_FOUND);
case SERVICE_UNAVAILABLE: // 503
throw new BusinessException(errMessage, ErrorResponse.TIME_OUT);
case METHOD_NOT_ALLOWED: // 405
case INTERNAL_SERVER_ERROR: // 500
default:
throw new BusinessException(errMessage,
ErrorResponse.INTERNAL_SERVER_ERROR);
}
}
Can someone lease help me to understand if it's the correct behaviour.
I suspect that if the response is 200 Success it should not go to the RestTemlate.class and RestTemplateErrorHandler.class
This behaviour is creating problem when API return 201 Created status, that time it goes to handleError() method and return the default case INTERNAL_SERVER_ERROR
Can someone please help me here
The following code will call the error handler every time the response is not 200 OK, event if it is successful like 201 Created.
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return clientHttpResponse.getStatusCode() != HttpStatus.OK;
}
Try changing the implementation to the following:
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return !clientHttpResponse.getStatusCode().is2xxSuccessful();
}
This is better suited for your needs as it will consider all 2xx status as successful requests instead of only 200 OK.
According to documentation method handleResponse() as it name suggests will handle the given response, perform appropriate logging and invoke the ResponseErrorHandler (if needed) which is interface used by the RestTemplate to determine whether a particular response has an error or not.
RestTemplateErrorHandler class implements implements ResponseErrorHandler.
If the hasError() method returns true then Spring will automatically call the handleError() method. This is the flow.
If you check implementation for handleResponse method, given below, you will see that there is a call to hasError method to check if the response has any errors. Default implementation of hasError method will return true is response code is 4XX or 5XX. If there is no errors, method will proceed execution and handleError method won't be invoked, as I explained above.
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
ResponseErrorHandler errorHandler = this.getErrorHandler();
boolean hasError = errorHandler.hasError(response);
if (this.logger.isDebugEnabled()) {
try {
this.logger.debug(method.name() + " request for \"" + url + "\" resulted in " + response.getRawStatusCode() + " (" + response.getStatusText() + ")" + (hasError ? "; invoking error handler" : ""));
} catch (IOException var7) {
;
}
}
if (hasError) {
errorHandler.handleError(url, method, response);
}
}
In code you posted hasError will return true for all response codes that are different from 200. That's why handleError is invoked.

Unable to catch error when using RestTemplate getForObject

I'm using RestTemplate to call my webservice's health actuator endpoint from another webservice of mine to see if the webservice is up. If the webservice is up, all works fine, but when it's down, I get an error 500, "Internal Server Error". If my webservice is down, I'm trying to catch that error to be able to handle it, but the problem I'm having is that I can't seem to be able to catch the error.
I've tried the following and it never enters either of my catch sections
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getBankAccountConnectorHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
String result = null;
try {
result = restTemplate.getForObject(uri, String.class);
} catch (HttpClientErrorException exception) {
System.out.println("callToRestService Error :" + exception.getResponseBodyAsString());
} catch (HttpStatusCodeException exception) {
System.out.println( "callToRestService Error :" + exception.getResponseBodyAsString());
}
System.out.println(result);
}
}
I've also tried doing it this way, but same results. It never seems to enter my error handler class.
public class NotFoundException extends RuntimeException {
}
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
System.out.println("Server error!");
} else if (httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
// handle CLIENT_ERROR
System.out.println("Client error!");
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getAccountHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
String result = null;
result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
}
Any suggestions as to how I can call my webservice's health actuator from another webservice and catch if that webservice is down?
It looks like the getForObject doesn't throw either of the exceptions you are catching. From the documentation, it throws RestClientException. The best method I have found for identifying thrown exceptions is to catch Exception in the debugger and inspect it to find out if it's useful.
With the second method, I'm not sure why you would create a bean method for the RestTemplate and then create one with new. You probably should inject your RestTemplate and initialise the ResponseErrorHandler with the RestTemplateBuilder::errorHandler method.
Internal serve error throw HttpServerErrorException You should catch this exception if you want to handle it However the better way to do that is using error handler you can see the following posts to see how to do that:
spring-rest-template-error-handling
spring-boot-resttemplate-error-handling

How can I make to throw this catch with JUnit

I got a function that make an update of an issue in jira and i want to throw the catch using JUnit.
This is the function that I got:
#PutMapping (value = "/update/{issueKey}")
public ResponseEntity<ResponseDTO>
updateIssue(#Validated #RequestBody EventDTO eventDTO, BindingResult result, #PathVariable String issueKey)
{
logger.info("Entra en /update con el payload: {}", eventDTO);
if (result.hasErrors())
{
ErrorResponseDTO errorResponseDTO = ErrorResponseDTO.getErrorResponseDTOFromBinding(result, messageSource);
return new ResponseEntity<>(errorResponseDTO, HttpStatus.BAD_REQUEST);
}
try
{
SuccessResponseDTO successResponseDTO = jiraService.update(eventDTO, issueKey);
logger.info("/update response {} ", successResponseDTO);
return new ResponseEntity<>(successResponseDTO, HttpStatus.OK);
}
catch (EywaException eywaException)
{
logger.error("Se ha producido un error en actualizar un issue", eywaException);
ErrorResponseDTO responseDTO = new ErrorResponseDTO();
String errorMessage = messageSource.getMessage(eywaException.getMessage(), null,
LocaleContextHolder.getLocale());
responseDTO.addErrorResponseDTO(eywaException.getMessage().split("\\.")[0], errorMessage);
return new ResponseEntity<>(responseDTO, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is what i have in the JUnit
#Test
public void updateIssue_NonExistanceIssue_shouldReturnFail() throws Exception
{
when(jiraService.update(eventDTO, "")).thenReturn(successResponseDTO);
String json = "{\"summary\":\""+eventDTO.getSummary()+"\""+
", \"description\":\""+eventDTO.getDescription()+"\""+
", \"raised\":\""+eventDTO.getRaised()+"\""+
", \"issueType\":\""+eventDTO.getIssueType()+"\""+
", \"priority\":\""+eventDTO.getPriority()+"\""+
", \"issuesubType\":\""+eventDTO.getIssuesubType()+"\""+
", \"region\":\""+eventDTO.getRegion()+"\""+
", \"airport\":\""+eventDTO.getAirport()+"\""+"}";
mvc.perform(put(BASE + UPDATE.replaceAll("\\{.*\\}", ""))
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isInternalServerError());
}
(I have already created the object in the setup)
The status i'm getting is the 405 and when i put an issueKey i got the status 200 (even when the issue key don't exist)
It has to throw status 500
I think that in the JUnit test you have to make .thenThrow(eywaException); instead of the .thenReturn(successResponseDTO); to make the test go through the exception and get the 500 status
I think your problem is the path variable.
The end point is "/update/{issueKey}" but during the test you call the "/update/" (?).
The Spring knows the first end-point, you call the second and spring cant find this end-point and throws 405 Method Not Allowed (You don't have PUT /update).

Handling custom error response in JAX-RS 2.0 client library

I am starting to use the new client API library in JAX-RS and really loving it so far. I have found one thing I cannot figure out however. The API I am using has a custom error message format that looks like this for example:
{
"code": 400,
"message": "This is a message which describes why there was a code 400."
}
It returns 400 as the status code but also includes a descriptive error message to tell you what you did wrong.
However the JAX-RS 2.0 client is re-mapping the 400 status into something generic and I lose the good error message. It correctly maps it to a BadRequestException, but with a generic "HTTP 400 Bad Request" message.
javax.ws.rs.BadRequestException: HTTP 400 Bad Request
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:908)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:770)
at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:90)
at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:671)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:424)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:667)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:396)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:296)
Is there some sort of interceptor or custom error handler that can be injected so that I get access to the real error message. I've been looking through documentation but can't see any way of doing it.
I am using Jersey right now, but I tried this using CXF and got the same result. Here is what the code looks like.
Client client = ClientBuilder.newClient().register(JacksonFeature.class).register(GzipInterceptor.class);
WebTarget target = client.target("https://somesite.com").path("/api/test");
Invocation.Builder builder = target.request()
.header("some_header", value)
.accept(MediaType.APPLICATION_JSON_TYPE)
.acceptEncoding("gzip");
MyEntity entity = builder.get(MyEntity.class);
UPDATE:
I implemented the solution listed in the comment below. It is slightly different since the classes have changed a bit in the JAX-RS 2.0 client API. I still think it is wrong that the default behavior is to give a generic error message and discard the real one. I understand why it wouldn't parse my error object, but the un-parsed version should have been returned. I end up having the replicate exception mapping that the library already does.
Thanks for the help.
Here is my filter class:
#Provider
public class ErrorResponseFilter implements ClientResponseFilter {
private static ObjectMapper _MAPPER = new ObjectMapper();
#Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
// for non-200 response, deal with the custom error messages
if (responseContext.getStatus() != Response.Status.OK.getStatusCode()) {
if (responseContext.hasEntity()) {
// get the "real" error message
ErrorResponse error = _MAPPER.readValue(responseContext.getEntityStream(), ErrorResponse.class);
String message = error.getMessage();
Response.Status status = Response.Status.fromStatusCode(responseContext.getStatus());
WebApplicationException webAppException;
switch (status) {
case BAD_REQUEST:
webAppException = new BadRequestException(message);
break;
case UNAUTHORIZED:
webAppException = new NotAuthorizedException(message);
break;
case FORBIDDEN:
webAppException = new ForbiddenException(message);
break;
case NOT_FOUND:
webAppException = new NotFoundException(message);
break;
case METHOD_NOT_ALLOWED:
webAppException = new NotAllowedException(message);
break;
case NOT_ACCEPTABLE:
webAppException = new NotAcceptableException(message);
break;
case UNSUPPORTED_MEDIA_TYPE:
webAppException = new NotSupportedException(message);
break;
case INTERNAL_SERVER_ERROR:
webAppException = new InternalServerErrorException(message);
break;
case SERVICE_UNAVAILABLE:
webAppException = new ServiceUnavailableException(message);
break;
default:
webAppException = new WebApplicationException(message);
}
throw webAppException;
}
}
}
}
I believe you want to do something like this:
Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
System.out.println( response.getStatusType() );
return null;
}
return response.readEntity( MyEntity.class );
Another thing you can try (since I don't know where this API puts stuff -- i.e. in the header or entity or what) is:
Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
// if they put the custom error stuff in the entity
System.out.println( response.readEntity( String.class ) );
return null;
}
return response.readEntity( MyEntity.class );
If you would like to generally map REST response codes to Java exception you can add a client filter to do that:
class ClientResponseLoggingFilter implements ClientResponseFilter {
#Override
public void filter(final ClientRequestContext reqCtx,
final ClientResponseContext resCtx) throws IOException {
if ( resCtx.getStatus() == Response.Status.BAD_REQUEST.getStatusCode() ) {
throw new MyClientException( resCtx.getStatusInfo() );
}
...
In the above filter you can create specific exceptions for each code or create one generic exception type that wraps the Response code and entity.
There are other ways to getting a custom error message to the Jersey client besides writing a custom filter. (although the filter is an excellent solution)
1) Pass error message in an HTTP header field.
The detail error message could be in the JSON response and in an additional header field, such as "x-error-message".
The Server adds the HTTP error header.
ResponseBuilder rb = Response.status(respCode.getCode()).entity(resp);
if (!StringUtils.isEmpty(errMsg)){
rb.header("x-error-message", errMsg);
}
return rb.build();
The Client catches the exception, NotFoundException in my case, and reads the response header.
try {
Integer accountId = 2222;
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target("http://localhost:8080/rest-jersey/rest");
webTarget = webTarget.path("/accounts/"+ accountId);
Invocation.Builder ib = webTarget.request(MediaType.APPLICATION_JSON);
Account resp = ib.get(new GenericType<Account>() {
});
} catch (NotFoundException e) {
String errorMsg = e.getResponse().getHeaderString("x-error-message");
// do whatever ...
return;
}
2) Another solution is to catch the exception and read the response content.
try {
// same as above ...
} catch (NotFoundException e) {
String respString = e.getResponse().readEntity(String.class);
// you can convert to JSON or search for error message in String ...
return;
}
The class WebApplicationException was designed for that but for some reason it ignores and overwrites what you specify as parameter for the message.
For that reason I created my own extension WebAppException that honors the parameters. It is a single class and it doesn't require any response filter or a mapper.
I prefer exceptions than creating a Response as it can be thrown from anywhere while processing.
Simple usage:
throw new WebAppException(Status.BAD_REQUEST, "Field 'name' is missing.");
The class:
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;
public class WebAppException extends WebApplicationException {
private static final long serialVersionUID = -9079411854450419091L;
public static class MyStatus implements StatusType {
final int statusCode;
final String reasonPhrase;
public MyStatus(int statusCode, String reasonPhrase) {
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
#Override
public int getStatusCode() {
return statusCode;
}
#Override
public Family getFamily() {
return Family.familyOf(statusCode);
}
#Override
public String getReasonPhrase() {
return reasonPhrase;
}
}
public WebAppException() {
}
public WebAppException(int status) {
super(status);
}
public WebAppException(Response response) {
super(response);
}
public WebAppException(Status status) {
super(status);
}
public WebAppException(String message, Response response) {
super(message, response);
}
public WebAppException(int status, String message) {
super(message, Response.status(new MyStatus(status, message)). build());
}
public WebAppException(Status status, String message) {
this(status.getStatusCode(), message);
}
public WebAppException(String message) {
this(500, message);
}
}
A much more concise solution for anyone stumbling on this:
Calling .get(Class<T> responseType) or any of the other methods that take the result type as an argument Invocation.Builder will return a value of the desired type instead of a Response. As a side effect, these methods will check if the received status code is in the 2xx range and throw an appropriate WebApplicationException otherwise.
From the documentation:
Throws: WebApplicationException in case the response status code of
the response returned by the server is not successful and the
specified response type is not Response.
This allows to catch the WebApplicationException, retrieve the actual Response, process the contained entity as exception details (ApiExceptionInfo) and throw an appropriate exception (ApiException).
public <Result> Result get(String path, Class<Result> resultType) {
return perform("GET", path, null, resultType);
}
public <Result> Result post(String path, Object content, Class<Result> resultType) {
return perform("POST", path, content, resultType);
}
private <Result> Result perform(String method, String path, Object content, Class<Result> resultType) {
try {
Entity<Object> entity = null == content ? null : Entity.entity(content, MediaType.APPLICATION_JSON);
return client.target(uri).path(path).request(MediaType.APPLICATION_JSON).method(method, entity, resultType);
} catch (WebApplicationException webApplicationException) {
Response response = webApplicationException.getResponse();
if (response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
throw new ApiException(response.readEntity(ApiExceptionInfo.class), webApplicationException);
} else {
throw webApplicationException;
}
}
}
ApiExceptionInfo is custom data type in my application:
import lombok.Data;
#Data
public class ApiExceptionInfo {
private int code;
private String message;
}
ApiException is custom exception type in my application:
import lombok.Getter;
public class ApiException extends RuntimeException {
#Getter
private final ApiExceptionInfo info;
public ApiException(ApiExceptionInfo info, Exception cause) {
super(info.toString(), cause);
this.info = info;
}
}
[At least with Resteasy] there is one big disadvantage with the solution offered by #Chuck M and based on ClientResponseFilter.
When you use it based on ClientResponseFilter, your BadRequestException, NotAuthorizedException, ... exceptions are wrapped by javax.ws.rs.ProcessingException.
Clients of your proxy must not be forced to catch this javax.ws.rs.ResponseProcessingException exception.
Without filter, we get an original rest exception. If we catch and handle by default, it does not give us much:
catch (WebApplicationException e) {
//does not return response body:
e.toString();
// returns null:
e.getCause();
}
The problem can be solved on another level, when you extract a description from the error. WebApplicationException exception, which is a parent for all rest exceptions, contains javax.ws.rs.core.Response. Just write a helper method, that in case the exception is of WebApplicationException type, it will also check the response body. Here is a code in Scala, but the idea should be clear. The methord returns a clear description of the rest exception:
private def descriptiveWebException2String(t: WebApplicationException): String = {
if (t.getResponse.hasEntity)
s"${t.toString}. Response: ${t.getResponse.readEntity(classOf[String])}"
else t.toString
}
Now we move a responsibility to show exact error, on the client. Just use a shared exception handler to minimize effort for clients.
The following works for me
Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();

Categories

Resources