Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Returning Mono<ResponseEntity<?>> causes NPE [SPR-14877] #19443

Closed
spring-projects-issues opened this issue Nov 3, 2016 · 3 comments
Closed

Returning Mono<ResponseEntity<?>> causes NPE [SPR-14877] #19443

spring-projects-issues opened this issue Nov 3, 2016 · 3 comments
Assignees
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Nov 3, 2016

Abhijit Sarkar opened SPR-14877 and commented

@RequestMapping(path = "/feign/**", produces = APPLICATION_JSON_VALUE)
    Mono<ResponseEntity<?>> all(Request request) {
        Mono<Response> response = feignService.findFeignMapping(request);

        return response.map(r -> {
            ResponseProperties rp = r.getResponseProperties();

            HttpHeaders httpHeaders = new HttpHeaders();

            if (!isEmpty(rp.getHeaders())) {
                rp.getHeaders().entrySet()
                        .forEach(e -> httpHeaders.put(e.getKey(), singletonList(e.getValue())));
            }

            Body responseBody = rp.getBody();

            if (!isEmpty(responseBody.toString())) {
                return ResponseEntity.status(rp.getStatus())
                        .headers(httpHeaders)
                        .body(responseBody.getContent());
            }

            return ResponseEntity.status(rp.getStatus())
                    .headers(httpHeaders)
                    .build();
        }).defaultIfEmpty(ResponseEntity.notFound().build());
    }
java.lang.NullPointerException: null
	at java.lang.Class.isAssignableFrom(Native Method) ~[na:1.8.0_66]
	at org.springframework.core.codec.ByteArrayEncoder.canEncode(ByteArrayEncoder.java:46) ~[spring-core-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
	at org.springframework.http.codec.EncoderHttpMessageWriter.canWrite(EncoderHttpMessageWriter.java:64) ~[spring-web-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
	at org.springframework.web.reactive.result.method.annotation.AbstractMessageWriterResultHandler.lambda$getProducibleMediaTypes$0(AbstractMessageWriterResultHandler.java:142) ~[spring-web-reactive-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174) ~[na:1.8.0_66]
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) ~[na:1.8.0_66]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_66]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_66]
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_66]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_66]
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_66]
	at org.springframework.web.reactive.result.method.annotation.AbstractMessageWriterResultHandler.getProducibleMediaTypes(AbstractMessageWriterResultHandler.java:144) ~[spring-web-reactive-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
	at org.springframework.web.reactive.result.method.annotation.AbstractMessageWriterResultHandler.writeBody(AbstractMessageWriterResultHandler.java:116) ~[spring-web-reactive-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
	at org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler.lambda$handleResult$2(ResponseEntityResultHandler.java:165) ~[spring-web-reactive-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]
	at reactor.core.publisher.MonoThenMap$MonoThenApplyMain.onNext(MonoThenMap.java:98) [reactor-core-3.0.3.RELEASE.jar:na]

Apparently, the code doesn't pay attention to the Accept header or RequestMapping.produces attribute but tries to use the generic parameter (wildcard ? in this case) to find an appropriate HttpMessageWriter. The workaround, as shown below, is to specify a type (String in this case) and not use the ResponseEntity builder.

@RequestMapping(path = "/feign/**", produces = APPLICATION_JSON_VALUE)
    Mono<ResponseEntity<String>> all(Request request) {
        Mono<Response> response = feignService.findFeignMapping(request);

        return response.map(r -> {
            ResponseProperties rp = r.getResponseProperties();

            HttpHeaders httpHeaders = new HttpHeaders();

            if (!isEmpty(rp.getHeaders())) {
                rp.getHeaders().entrySet()
                        .forEach(e -> httpHeaders.put(e.getKey(), singletonList(e.getValue())));
            }

            Body responseBody = rp.getBody();

            if (!isEmpty(responseBody.toString())) {
                return ResponseEntity.status(rp.getStatus())
                        .headers(httpHeaders)
                        .body(responseBody.getContent());
            }

            return new ResponseEntity<String>(httpHeaders, HttpStatus.valueOf(rp.getStatus()));
        }).defaultIfEmpty(new ResponseEntity<String>(HttpStatus.NOT_FOUND));
    } 

Affects: 5.0 M2

Issue Links:

  • #19506 ResponseEntity builders should allow casting an empty body to any type

Referenced from: commits c430402

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Nov 4, 2016

Rossen Stoyanchev commented

Indeed the AbstractMessageWriterResultHandler seems to collect all candidate producible types in order to pass them to the base class AbstractHandlerResultHandler which then does check the produces condition attribute. We can correct that by passing in a supplier of the candidate producible types.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Nov 4, 2016

Rossen Stoyanchev commented

I've made the above change to check the producible media type first and use it. However the body type is still required further below when we iterate to find the HttpMessageWriter to write with. For that I've made one additional change to check to fall back on the type of the actual body instance.

Note that you can still use the ResponseEntity builder although it's a bit more verbose: ResponseEntity.status(NOT_FOUND).body(null). We could separately probably consider an improvement to the ResponseEntityBuilder to allow casting the empty body.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Nov 23, 2016

Rossen Stoyanchev commented

This ResponseEntity builder improvement is now tracked under #19506.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants