Skip to content

Spring Webflux 406 errors when ContentType of Method differs from ExceptionHandler #13635

Closed
@pluttrell

Description

@pluttrell

I'm using Spring Webflux and seeing 406 errors when both of the following occur:

  1. Exceptions are returned from the the Reactive stream instead of the controller method itself.
  2. When the controller method produces a different ContentType then the ExceptionHandler.

This is reproducible using SpringBoot v2.0.3 and v2.1.0-BUILD-SNAPSHOT (as of today).

@RestController
@RequiredArgsConstructor
class BinaryTestController {

  private final AccessKeyValidationService accessKeyValidationService;

  @GetMapping(path = "/binary-test", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
  Mono<byte[]> test(@RequestParam("apiKey") String apiKey, @RequestParam(name = "failFirst", required = false) boolean failFirst) {

    if (failFirst) { throw new IllegalArgumentException();}

    return Mono.just(apiKey)
        .filterWhen(accessKeyValidationService::isValid)
        .switchIfEmpty(Mono.error(new IllegalAccessException()))
        .then(Mono.just("test".getBytes(StandardCharsets.UTF_8)));
  }

  @ExceptionHandler(IllegalArgumentException.class)
  ResponseEntity<Mono<BadResponse>> handleIllegalArgumentException() {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    return new ResponseEntity<>(Mono.just(new BadResponse("better luck next time")), headers, HttpStatus.BAD_REQUEST);
  }

  @ExceptionHandler(IllegalAccessException.class)
  ResponseEntity<Mono<BadResponse>> handleIllegalAccessException() {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    return new ResponseEntity<>(Mono.just(new BadResponse("access denied")), headers, HttpStatus.FORBIDDEN);
  }

  @Data
  class BadResponse {
    private final String developerMessage;
  }

}

The AccessKeyValidationService does a blocking lookup, so naturally needs to return a Mono and thus needs to be part of the Mono returned from the controller:

@Service
class AccessKeyValidationService {

  Mono<Boolean> isValid(String accessKey) {
    return Mono.defer(() -> Mono.just(blockingLookup(accessKey)))
        .subscribeOn(Schedulers.elastic());
  }

  private Boolean blockingLookup(String accessKey) {
    //Some blocking lookup...
    return accessKey.equals("good");
  }
}

A valid apiKey, results in proper binary output:

$ http localhost:8080/binary-test apiKey==good
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/octet-stream

test

When I throw an exception before the Reactive Stream, I get the intended JSON output:

$ http localhost:8080/binary-test apiKey==good failFirst==true
HTTP/1.1 400 Bad Request
Content-Length: 44
Content-Type: application/json;charset=UTF-8

{
    "developerMessage": "better luck next time"
}

But when I trigger the steam to error with an IllegalAccessException, I get a 406:

$ http localhost:8080/binary-test apiKey==bad
HTTP/1.1 406 Not Acceptable
Content-Length: 157
Content-Type: application/json;charset=UTF-8

{
    "error": "Not Acceptable",
    "message": "Could not find acceptable representation",
    "path": "/binary-test",
    "status": 406,
    "timestamp": "2018-06-30T05:23:09.820+0000"
}

If I put a breakpoint in the handleIllegalAccessException method, it stops as expected.

If I switch the return type for the controller method to JSON it works as expected.

Is there something else I need to do to switch the response type when erroring out the returned Mono? Or some other way to resolve it?

Here's a repo with a failing unit test reproducing the problem: https://github.com/pluttrell/spring-webflux-binary-response-exception-handling-example

Metadata

Metadata

Assignees

Labels

status: invalidAn issue that we don't feel is valid

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions