From 141df5291d236a0c787f02a2b55d0f6e3055e716 Mon Sep 17 00:00:00 2001 From: Edgar Asatryan Date: Sat, 11 Oct 2025 20:16:37 +0400 Subject: [PATCH 1/3] Prevent NoClassDefFoundError when Jetty Reactive HttpClient not on classpath Closes gh-35608 Signed-off-by: Edgar Asatryan --- .../test/web/reactive/server/DefaultWebTestClientBuilder.java | 4 +++- .../web/reactive/function/client/DefaultWebClientBuilder.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index 3ce339e20c1..6114a8bb0c9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -67,7 +67,9 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { ClassLoader loader = DefaultWebTestClientBuilder.class.getClassLoader(); reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader); reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", loader); - jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader); + jettyClientPresent = + ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader) && + ClassUtils.isPresent("org.eclipse.jetty.reactive.client.ReactiveRequest", loader); httpComponentsClientPresent = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader) && ClassUtils.isPresent("org.apache.hc.core5.reactive.ReactiveDataConsumer", loader); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java index 653d8d2ab4c..a09a707eb8f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java @@ -67,7 +67,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder { ClassLoader loader = DefaultWebClientBuilder.class.getClassLoader(); reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader); reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", loader); - jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader); + jettyClientPresent = + ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader) && + ClassUtils.isPresent("org.eclipse.jetty.reactive.client.ReactiveRequest", loader); httpComponentsClientPresent = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader) && ClassUtils.isPresent("org.apache.hc.core5.reactive.ReactiveDataConsumer", loader); From ba2bb0858969aebfe6d83090ecfbcb8c10356486 Mon Sep 17 00:00:00 2001 From: Marius Lichtblau Date: Mon, 13 Oct 2025 17:58:04 +0200 Subject: [PATCH 2/3] Release DataBuffer in AbstractCharSequenceDecoder if String creation fails See gh-35625 Signed-off-by: Marius Lichtblau --- .../codec/AbstractCharSequenceDecoder.java | 20 +++++++++++-------- .../io/buffer/DataBufferLimitException.java | 4 ++++ .../core/io/buffer/NettyDataBuffer.java | 7 ++++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/codec/AbstractCharSequenceDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/AbstractCharSequenceDecoder.java index 65fbdc51b0b..07dee48b0b9 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/AbstractCharSequenceDecoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/AbstractCharSequenceDecoder.java @@ -175,14 +175,18 @@ public abstract class AbstractCharSequenceDecoder extend public final T decode(DataBuffer dataBuffer, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) { - Charset charset = getCharset(mimeType); - T value = decodeInternal(dataBuffer, charset); - DataBufferUtils.release(dataBuffer); - LogFormatUtils.traceDebug(logger, traceOn -> { - String formatted = LogFormatUtils.formatValue(value, !traceOn); - return Hints.getLogPrefix(hints) + "Decoded " + formatted; - }); - return value; + try { + Charset charset = getCharset(mimeType); + T value = decodeInternal(dataBuffer, charset); + LogFormatUtils.traceDebug(logger, traceOn -> { + String formatted = LogFormatUtils.formatValue(value, !traceOn); + return Hints.getLogPrefix(hints) + "Decoded " + formatted; + }); + return value; + } + finally { + DataBufferUtils.release(dataBuffer); + } } private Charset getCharset(@Nullable MimeType mimeType) { diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java index eaeac1b9410..1ac65333368 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java @@ -35,4 +35,8 @@ public class DataBufferLimitException extends IllegalStateException { super(message); } + public DataBufferLimitException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java index 7ba28200d68..28a9eede4ac 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java @@ -379,7 +379,12 @@ public class NettyDataBuffer implements PooledDataBuffer { @Override public String toString() { - return this.byteBuf.toString(); + try { + return this.byteBuf.toString(); + } + catch (OutOfMemoryError ex) { + throw new DataBufferLimitException("Failed to convert data buffer to string: " + ex.getMessage(), ex); + } } From 05814f7a421508ad4c600780a641c3b3aa26f44b Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 14 Oct 2025 16:42:13 +0100 Subject: [PATCH 3/3] Polishing contribution Closes gh-35625 --- .../core/io/buffer/DataBufferLimitException.java | 7 +++++++ .../springframework/core/io/buffer/NettyDataBuffer.java | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java index 1ac65333368..93e403277e2 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferLimitException.java @@ -31,10 +31,17 @@ package org.springframework.core.io.buffer; public class DataBufferLimitException extends IllegalStateException { + /** + * Create an instance with the given message. + */ public DataBufferLimitException(String message) { super(message); } + /** + * Create an instance with a message and a cause, e.g. {@link OutOfMemoryError}. + * @since 6.2.12 + */ public DataBufferLimitException(String message, Throwable cause) { super(message, cause); } diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java index 28a9eede4ac..c6660d42a5f 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java @@ -383,7 +383,8 @@ public class NettyDataBuffer implements PooledDataBuffer { return this.byteBuf.toString(); } catch (OutOfMemoryError ex) { - throw new DataBufferLimitException("Failed to convert data buffer to string: " + ex.getMessage(), ex); + throw new DataBufferLimitException( + "Failed to convert data buffer to string: " + ex.getMessage(), ex); } }