Rob Rob - 1 month ago 5
reST (reStructuredText) Question

Http Error Messages Not Propagating from Microservice to Webapp

I'm building a spring boot microservice-based application and I'm having trouble getting my error messages to propagate from the services containing all my business logic back into the webapp. In my services I'm throwing exceptions that look like this:

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class Http400ServiceException extends Exception {

public Http400ServiceException() {
super("Some error message");
}
}


Everything behaves as expected, sending the response code as expected. So if my service sent a 403 exception, I'd get a 403 in the webapp. What I'm trying to do now is to get the error messages from the exceptions in my service.

The Problem

When I poke my services from a rest client in such a way as to produce a 403, the response (in JSON) looks like this:

{
"timestamp": 1459453512220
"status": 403
"error": "Forbidden"
"exception": "com.mysite.mypackage.exceptions.Http403ServiceException"
"message": "Username Rob must be between 5 and 16 characters long"
"path": "/createLogin"
}


However, for some reason I can't access the 'message' field from my webapp. I have some generic error handling code in the webapp that looks like this:

@Override
public RuntimeException handleException(Exception e) {
...
if (e instanceof HttpClientErrorException) {
HttpClientErrorException httpError = (HttpClientErrorException) e;
LOGGER.info(httpError.getResponseBodyAsString());
}
...
}


But when I look in my logs/run my app in debug,
httpError.getResponseBodyAsString()
is returning null. It's got the right response code, just no response body.

If anyone has any insights into what's going wrong, I'd really appreciate it.

Rob Rob
Answer

So we parked this issue for a few months while we were working on other areas of the app. But just in case someone else sees this while trying to solve a similar problem, the approach I ended up taking was the following

  • Create an interface for all responses from all services to implement, and an exception type to indicate that the exception is because of user error:

    public interface IModel {
        boolean isError();
        UserException getError();
    }
    
  • In the controllers for each service, catch any exceptions and create some sort of IModel out of them:

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(UserException.class)
    public IModel handleException(UserException exception) {
        return exception.toModel();
    }
    
  • In the component used to call the services, if the response has a UserException on it, throw it, otherwise return the response body:

    public <T extends IModel> T makeCall(Object payload, Endpoint<T> endpoint) throws UserException {
        ...
        ResponseEntity<T> response = restTemplate.postForEntity(endpoint.getEndpointUrl(), payload, endpoint.getReturnType());
        if (response.getBody().isError()) {
            throw response.getBody().getError();
        }
        ...
        return response.getBody();
    }
    
  • In the webapp, handle the exceptions with the appropriate @ExceptionHandler e.g.

    @ExceptionHandler(UserException.class)
    public ModelAndView handleUserException(UserException e) {
        ModelAndView modelAndView = new ModelAndView("viewName");
        modelAndView.addObject("errorMessage", e.getMessage());
        return modelAndView;
    }
    

I kinda feel like there's a cleaner approach but this is the best I've been able to come up with so far. I'll update this if I find a better way of doing it though.