Skip to content

Commit 06d267f

Browse files
committed
Improve WebClientResponseException message in case of 1xx/2xx/3xx status
When a response fails to be completely emitted by the remote (connection termination during the transmission of the response for example), a WebClientResponseException can be propagated with a confusing message which mainly reflects the status code and reason phrase, leading to messages like "200 OK" in such an exception. This change improves the situation by appending a hint at the underlying cause whenever getMessage() is called on a WebClientResponseException which was created with a non-error status code. Closes gh-33127
1 parent dfa6b4b commit 06d267f

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java

+13
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,19 @@ public void setBodyDecodeFunction(Function<ResolvableType, ?> decoderFunction) {
272272
this.bodyDecodeFunction = decoderFunction;
273273
}
274274

275+
@Override
276+
public String getMessage() {
277+
if (shouldHintAtResponseFailure()) {
278+
return super.getMessage() + ", but response failed with cause: " + getCause();
279+
}
280+
return super.getMessage();
281+
}
282+
283+
private boolean shouldHintAtResponseFailure() {
284+
return this.statusCode.is1xxInformational() ||
285+
this.statusCode.is2xxSuccessful() ||
286+
this.statusCode.is3xxRedirection();
287+
}
275288

276289
/**
277290
* Create {@code WebClientResponseException} or an HTTP status specific subclass.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function.client;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
class WebClientResponseExceptionTests {
24+
25+
@Test
26+
void constructWithSuccessStatusCodeAndNoCauseAdditionalMessage() {
27+
assertThat(new WebClientResponseException(200, "OK", null, null, null))
28+
.hasNoCause()
29+
.hasMessage("200 OK, but response failed with cause: null");
30+
}
31+
32+
@Test
33+
void constructWith1xxStatusCodeAndCauseAdditionalMessage() {
34+
final WebClientResponseException ex = new WebClientResponseException(100, "reasonPhrase", null, null, null);
35+
ex.initCause(new RuntimeException("example cause"));
36+
assertThat(ex).hasMessage("100 reasonPhrase, but response failed with cause: java.lang.RuntimeException: example cause");
37+
}
38+
39+
@Test
40+
void constructWith2xxStatusCodeAndCauseAdditionalMessage() {
41+
final WebClientResponseException ex = new WebClientResponseException(200, "reasonPhrase", null, null, null);
42+
ex.initCause(new RuntimeException("example cause"));
43+
assertThat(ex).hasMessage("200 reasonPhrase, but response failed with cause: java.lang.RuntimeException: example cause");
44+
}
45+
46+
@Test
47+
void constructWith3xxStatusCodeAndCauseAdditionalMessage() {
48+
final WebClientResponseException ex = new WebClientResponseException(300, "reasonPhrase", null, null, null);
49+
ex.initCause(new RuntimeException("example cause"));
50+
assertThat(ex).hasMessage("300 reasonPhrase, but response failed with cause: java.lang.RuntimeException: example cause");
51+
}
52+
53+
@Test
54+
void constructWithExplicitMessageAndNotErrorCodeAdditionalMessage() {
55+
final WebClientResponseException ex = new WebClientResponseException("explicit message", 100, "reasonPhrase", null, null, null);
56+
assertThat(ex).hasMessage("explicit message, but response failed with cause: null");
57+
}
58+
59+
@Test
60+
void constructWithExplicitMessageAndNotErrorCodeAndCauseAdditionalMessage() {
61+
final WebClientResponseException ex = new WebClientResponseException("explicit message", 100, "reasonPhrase", null, null, null);
62+
ex.initCause(new RuntimeException("example cause"));
63+
assertThat(ex).hasMessage("explicit message, but response failed with cause: java.lang.RuntimeException: example cause")
64+
.hasRootCauseMessage("example cause");
65+
}
66+
67+
@Test
68+
void constructWithExplicitMessageAndErrorCodeAndCauseNoAdditionalMessage() {
69+
final WebClientResponseException ex = new WebClientResponseException("explicit message", 404, "reasonPhrase", null, null, null);
70+
ex.initCause(new RuntimeException("example cause"));
71+
assertThat(ex).hasMessage("explicit message").hasRootCauseMessage("example cause");
72+
}
73+
74+
@Test
75+
void constructWith4xxStatusCodeAndCauseNoAdditionalMessage() {
76+
final WebClientResponseException ex = new WebClientResponseException(400, "reasonPhrase", null, null, null);
77+
ex.initCause(new RuntimeException("example cause"));
78+
assertThat(ex).hasMessage("400 reasonPhrase").hasRootCauseMessage("example cause");
79+
}
80+
81+
@Test
82+
void constructWith5xxStatusCodeAndCauseNoAdditionalMessage() {
83+
final WebClientResponseException ex = new WebClientResponseException(500, "reasonPhrase", null, null, null);
84+
ex.initCause(new RuntimeException("example cause"));
85+
assertThat(ex).hasMessage("500 reasonPhrase").hasRootCauseMessage("example cause");
86+
}
87+
}

0 commit comments

Comments
 (0)