Background

/**
 * Global option to specify a header to be added to every request,
 * if the request does not already contain such a header.
 */
Builder defaultHeader(String header, String... values);

WebClient and RestClient have default[Header|Cookie|Request|..] that useful to set default value on request.

https://github.com/spring-projects/spring-framework/blob/899de4f3bfc373bdf1c0e67c7160331bd056825b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java#L502-L508

As you can see above code, defaultHeader value is set only when it's not contained in headers.

Question

/**
 * Provide a consumer to customize every request being built.
 * @param defaultRequest the consumer to use for modifying requests
 */
Builder defaultRequest(Consumer<RequestHeadersSpec<?>> defaultRequest);

https://github.com/spring-projects/spring-framework/blob/899de4f3bfc373bdf1c0e67c7160331bd056825b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java#L481-L484

But on defaultRequest(..), it can override request's values unlike defaultHeader cause it's called on every request being built.

Reproduce

// DefaultWebClientTests.java
@Test
void defaultRequest_override() {
    ThreadLocal<String> context = new NamedThreadLocal<>("foo");

    WebClient client = this.builder
            .defaultRequest(spec -> spec.accept(MediaType.APPLICATION_JSON)) // default
            .build();

    client.get().uri("/path")
            .accept(MediaType.IMAGE_PNG) // set
            .retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10));

    ClientRequest request = verifyAndGetRequest();
    assertThat(request.headers().getAccept()).isEqualTo(MediaType.IMAGE_PNG); // ❌Expected: <image/png> but actual: <application/json>
}

Spring WebClient and RestClient's defaultRequest(..) is not invoked early enough

I think defaultRequest(spec -> spec.accept(MEDIA_TYPE)) will override all media types that set by accept(..) on this request.

Q. is above behavior of defaultRequest(..) is intended? thanks!

Comment From: injae-kim

FYI, I found this issue while investigating https://github.com/spring-projects/spring-framework/issues/32028 😃

If this behavior of defaultRequest(..) is intended, feel free to close this issue~! (I simply think that defaultRequest works similarly with defaultHeader, setting default value)

Comment From: poutsma

Thanks for spotting this odd behavior, which is indeed not intentional. However, the fact remains that the current behavior has been present since 5.0, and changing it, to do effectively the opposite of what it is currently doing, can affect many users.

Fixing this issue is trivial—I have a fix locally—but that fix won't be merged until 6.2.

Comment From: poutsma

Note that—even though this is a bug—it will not be backported to the 6.1, 6.0, or 5.3 branches, as it fixing it will break backward compatibility.

Comment From: injae-kim

even though this is a bug—it will not be backported to the 6.1, 6.0, or 5.3 branches, as it fixing it will break backward compatibility.

Aha I understood! I agree with your opinion. I just wondered it's bug or intended behavior.

Anyway I'm glad to found this trivial bug, and please share if there's something I can help~ thanks! 🙇