I built a function in a spring-boot app that consisted of a controller, service layer and repository. I wanted to ensure that when a user sent two values to the controller, it would throw an error back to the user, with an appropriate status code without having to pass the data on to the service and have it continue. The aim was to find an annotation that Spring could use to return a specific status code of my choosing with a reason. So, a bit of digging into the documentation and I found @ResponseStatus

Trying @ResponseStatus
@ApiOperation("Add a friend")
@RequestMapping(value = "/addFriends", method = RequestMethod.GET)
public User addFriends(@RequestParam final String userUsername, @RequestParam final String otherUserUsername) {
    if(!userUsername.equals(otherUserUsername)) {
        return userService.addFriend(userService.findByUsername(userUsername).get(), userService.findByUsername(otherUserUsername).get());
    } else {
        throw new UserIsSelfException("message");
    }
}
@ResponseStatus(value= HttpStatus.FORBIDDEN, reason="Can't add yourself")  // 403
public class UserIsSelfException extends RuntimeException {
    public UserIsSelfException(String message) {
        super(message);
    }
}
What you'd expect from this is the controller to return a 403 status code:

But what actually happens is Spring Boot returns a 200 status code! What!?

This might be fine for someone looking at a web page, but for any service consuming the API, this makes things very hard to parse.

So I read up on the JavaDoc and found this:

The important part there is “the response is considered complete”. I personally think this must be an implementation bug when they wrote the method, because the response body is the message that we expect, but with a 200 status code.

So…what do we do?

What Others Use

For the people that identified this…let’s call it, “unsusual feature”… Spring introduced @ControllerAdvice like this:

@ControllerAdvice
class ExceptionHandlerAdvice {
@ExceptionHandler
@ResponseBody
ExceptionRepresentation handle(Exception exception) {
 ExceptionRepresentation body = new ExceptionRepresentation(exception.getLocalizedMessage());
 HttpStatus responseStatus = resolveAnnotatedResponseStatus(exception);
 return new ResponseEntity<ExceptionRepresentation>(body, responseStatus);
}

The problem with this, is that you are then required to implement a lot of the exception handling yourself and furthermore, this advice works accross all controllers on the classpath. I simply wanted to return a status code from one single controller.

Utilising a standard HTTP 500

Another solution, and the one that I chose, was to simply make the server return a standard “Internal Server Error” 500 response code by throwing an existing exception. This worked well for my particular use case, because passing two of the same user could be seen as Illegal Arguments. Also, we can still put our own custom response there, which we can parse in an API and handle for accordingly.

@ApiOperation("Add a friend")
@RequestMapping(value = "/addFriends", method = RequestMethod.GET)
public User addFriends(@RequestParam final String userUsername, @RequestParam final String otherUserUsername) {
    if(!userUsername.equals(otherUserUsername)) {
        return userService.addFriend(userService.findByUsername(userUsername).get(), userService.findByUsername(otherUserUsername).get());
    } else {
        throw new IllegalArgumentException("User attempting to add themselves");
    }
}

Let me know if this helped!


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *