Skip to content

NullPointerException is thrown if WebClient is used with Apache Httpclient and cookies are disabled #34132

@pmv

Description

@pmv

Running this unit test:

(note the disableCookieManagement() option)

import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class WebClientTest {

    @Test
    void cookiesDisabledTest() {
        CloseableHttpAsyncClient noCookieHttpClient = HttpAsyncClientBuilder.create()
                .disableCookieManagement().build();
        WebClient webClient = WebClient.builder()
                .clientConnector(new HttpComponentsClientHttpConnector(noCookieHttpClient))
                .build();
        webClient.get().uri("https://spring.io/").exchange().block();
    }
}

throws this exception:

org.springframework.web.reactive.function.client.WebClientRequestException: Cannot invoke "org.apache.hc.client5.http.cookie.CookieSpec.parse(org.apache.hc.core5.http.Header, org.apache.hc.client5.http.cookie.CookieOrigin)" because "cookieSpec" is null

	at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:137)
	Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ Request to GET https://spring.io/ [DefaultWebClient]
Original Stack Trace:
		at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:137)
		at reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:55)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onError(MonoIgnoreThen.java:280)
		at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:205)
		at org.springframework.http.client.reactive.HttpComponentsClientHttpConnector$ResultCallback.failed(HttpComponentsClientHttpConnector.java:186)
		at org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:166)
		at org.apache.hc.core5.concurrent.ComplexFuture.failed(ComplexFuture.java:79)
		at org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient$2$1.failed(InternalAbstractHttpAsyncClient.java:315)
		at org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:166)
		at org.apache.hc.core5.reactive.ReactiveResponseConsumer.failed(ReactiveResponseConsumer.java:143)
		at org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient$2.failed(InternalAbstractHttpAsyncClient.java:353)
		at org.apache.hc.client5.http.impl.async.AsyncRedirectExec$1.failed(AsyncRedirectExec.java:246)
		at org.apache.hc.client5.http.impl.async.AsyncHttpRequestRetryExec$1.failed(AsyncHttpRequestRetryExec.java:195)
		at org.apache.hc.client5.http.impl.async.AsyncProtocolExec$1.failed(AsyncProtocolExec.java:295)
		at org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.failed(HttpAsyncMainClientExec.java:136)
		at org.apache.hc.client5.http.impl.async.LoggingAsyncClientExchangeHandler.failed(LoggingAsyncClientExchangeHandler.java:177)
		at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamHandler.failed(ClientHttp1StreamHandler.java:291)
		at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer.terminate(ClientHttp1StreamDuplexer.java:181)
		at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.shutdownSession(AbstractHttp1StreamDuplexer.java:165)
		at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onException(AbstractHttp1StreamDuplexer.java:407)
		at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.exception(AbstractHttp1IOEventHandler.java:90)
		at org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler.exception(ClientHttp1IOEventHandler.java:41)
		at org.apache.hc.client5.http.impl.async.LoggingIOSession$1.exception(LoggingIOSession.java:253)
		at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.exception(SSLIOSession.java:243)
		at org.apache.hc.core5.reactor.InternalDataChannel.onException(InternalDataChannel.java:181)
		at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:55)
		at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:176)
		at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:125)
		at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:92)
		at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
		at java.base/java.lang.Thread.run(Thread.java:1583)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:104)
		at reactor.core.publisher.Mono.block(Mono.java:1779)
		at WebClientTest.cookiesDisabledTest(WebClientTest.java:16)
		at java.base/java.lang.reflect.Method.invoke(Method.java:580)
		at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
		at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.NullPointerException: Cannot invoke "org.apache.hc.client5.http.cookie.CookieSpec.parse(org.apache.hc.core5.http.Header, org.apache.hc.client5.http.cookie.CookieOrigin)" because "cookieSpec" is null
	at org.springframework.http.client.reactive.HttpComponentsClientHttpResponse.adaptCookies(HttpComponentsClientHttpResponse.java:79)
	at org.springframework.http.client.reactive.HttpComponentsClientHttpResponse.<init>(HttpComponentsClientHttpResponse.java:62)
	at org.springframework.http.client.reactive.HttpComponentsClientHttpConnector$ResponseCallback.completed(HttpComponentsClientHttpConnector.java:153)
	at org.springframework.http.client.reactive.HttpComponentsClientHttpConnector$ResponseCallback.completed(HttpComponentsClientHttpConnector.java:135)
	at org.apache.hc.core5.concurrent.BasicFuture.completed(BasicFuture.java:148)
	at org.apache.hc.core5.reactive.ReactiveResponseConsumer.consumeResponse(ReactiveResponseConsumer.java:127)
	at org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient$2.handleResponse(InternalAbstractHttpAsyncClient.java:305)
	at org.apache.hc.client5.http.impl.async.AsyncRedirectExec$1.handleResponse(AsyncRedirectExec.java:208)
	at org.apache.hc.client5.http.impl.async.AsyncHttpRequestRetryExec$1.handleResponse(AsyncHttpRequestRetryExec.java:134)
	at org.apache.hc.client5.http.impl.async.AsyncProtocolExec$1.handleResponse(AsyncProtocolExec.java:230)
	at org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.consumeResponse(HttpAsyncMainClientExec.java:243)
	at org.apache.hc.client5.http.impl.async.LoggingAsyncClientExchangeHandler.consumeResponse(LoggingAsyncClientExchangeHandler.java:142)
	at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamHandler.consumeHeader(ClientHttp1StreamHandler.java:249)
	at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer.consumeHeader(ClientHttp1StreamDuplexer.java:348)
	at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer.consumeHeader(ClientHttp1StreamDuplexer.java:80)
	at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onInput(AbstractHttp1StreamDuplexer.java:299)
	at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.inputReady(AbstractHttp1IOEventHandler.java:64)
	at org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler.inputReady(ClientHttp1IOEventHandler.java:41)
	at org.apache.hc.client5.http.impl.async.LoggingIOSession$1.inputReady(LoggingIOSession.java:238)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.decryptData(SSLIOSession.java:617)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$200(SSLIOSession.java:74)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:202)
	at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:143)
	at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51)
	at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:176)
	at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:125)
	at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:92)
	at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
	at java.base/java.lang.Thread.run(Thread.java:1583)

When cookies are disabled, it seems there is no CookieSpec in the context here:
https://github.com/spring-projects/spring-framework/blob/v6.2.1/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java#L72

so the exception happens here:
https://github.com/spring-projects/spring-framework/blob/v6.2.1/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java#L79

I assume this is a legitimate use case? In the case of a null CookieSpec, an empty map could be returned.

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions