spring-cloud / spring-cloud/spring-cloud-gateway

Multipart Upload Issue After Spring Boot 3.3.5 Upgrade – Connection Closed by Peer

Open
#3,632 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

waiting-for-triage
Dominant language
Java
Stars
4.9k
Forks
3.5k
Avg merge
20h 57m
Merged PRs (30d)
8

Description

Hello Experts,

We’ve encountered an issue with multipart/form-data uploads in our microservices architecture after upgrading from Spring Boot 3.3.2 (where uploads worked as expected) to Spring Boot 3.3.5 and Spring Cloud 2024.0.3. Below is a quick overview:

Architecture:

Service A: Receives the multipart uploads.
Cloud Gateway: Acts as a proxy Service A, forwarding requests.
Service B: Uses Reactive Web Client to upload files to another service.

Evidence:

  • Small files (e.g., 10 records) upload successfully using service B reactive web client and failed with large size files.

  • The same large files upload successfully via Postman to the Service A proxy endpoint

Problem:

Service A log: Since the upgrade, Service A throws EOFException when handling larger multipart file uploads

Service B log:

org.springframework.web.reactive.function.client.WebClientRequestException: Connection closed by peer
	at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136) ~[spring-webflux-6.1.14.jar:6.1.14]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ Request to POST https://serviceUrl/import [DefaultWebClient]
Original Stack Trace:
		at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136) ~[spring-webflux-6.1.14.jar:6.1.14]
		at reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:55) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onError(MonoIgnoreThen.java:280) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:205) ~[reactor-core-3.6.11.jar:3.6.11]
		at org.springframework.http.client.reactive.HttpComponentsClientHttpConnector$ResponseCallback.failed(HttpComponentsClientHttpConnector.java:161) ~[spring-web-6.1.14.jar:6.1.14]
		at org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:138) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactive.ReactiveResponseConsumer.failed(ReactiveResponseConsumer.java:141) ~[httpcore5-reactive-5.2.5.jar:5.2.5]
		at org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient$2.failed(InternalAbstractHttpAsyncClient.java:347) ~[httpclient5-5.3.1.jar:5.3.1]
		at org.apache.hc.client5.http.impl.async.AsyncRedirectExec$1.failed(AsyncRedirectExec.java:248) ~[httpclient5-5.3.1.jar:5.3.1]
		at org.apache.hc.client5.http.impl.async.AsyncProtocolExec$1.failed(AsyncProtocolExec.java:295) ~[httpclient5-5.3.1.jar:5.3.1]
		at org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.failed(HttpAsyncMainClientExec.java:131) ~[httpclient5-5.3.1.jar:5.3.1]
		at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamHandler.failed(ClientHttp1StreamHandler.java:285) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer.terminate(ClientHttp1StreamDuplexer.java:193) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.shutdownSession(AbstractHttp1StreamDuplexer.java:163) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onInput(AbstractHttp1StreamDuplexer.java:349) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.inputReady(AbstractHttp1IOEventHandler.java:64) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler.inputReady(ClientHttp1IOEventHandler.java:41) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.ssl.SSLIOSession.decryptData(SSLIOSession.java:633) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$200(SSLIOSession.java:74) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:202) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:142) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:178) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:127) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:86) ~[httpcore5-5.2.5.jar:5.2.5]
		at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44) ~[httpcore5-5.2.5.jar:5.2.5]
		at java.base/java.lang.Thread.run(Thread.java:1570) [?:?]
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:104) ~[reactor-core-3.6.11.jar:3.6.11]
		at reactor.core.publisher.Mono.block(Mono.java:1779) ~[reactor-core-3.6.11.jar:3.6.11]
		at se.cambio.eco.ts.terminology.TerminologyClient.importCodeSystem$lambda$16(TerminologyClient.kt:143) ~[main/:?]
		at se.cambio.eco.ts.terminology.TerminologyClient.wrap(TerminologyClient.kt:36) ~[main/:?]
		at se.cambio.eco.ts.terminology.TerminologyClient.importCodeSystem(TerminologyClient.kt:129) ~[main/:?]
		at se.cambio.eco.ts.workflow.CodeSystemImport.processCodeSystemImport(CodeSystemImport.kt:131) ~[main/:?]
		at se.cambio.eco.ts.workflow.CodeSystemImport.createCodeSystem(CodeSystemImport.kt:76) ~[main/:?]
		at se.cambio.eco.ts.queue.JobScheduler$handleJobs$1.invokeSuspend(JobScheduler.kt:53) ~[main/:?]
		at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith$$$capture(ContinuationImpl.kt:33) ~[kotlin-stdlib-2.0.0.jar:2.0.0-release-341]
		at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) ~[kotlin-stdlib-2.0.0.jar:2.0.0-release-341]
		at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.8.1.jar:?]
		at se.cambio.eco.ts.queue.JobScheduler.handleJobs(JobScheduler.kt:38) ~[main/:?]
		at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
		at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
		at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130) ~[spring-context-6.1.14.jar:6.1.14]
		at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124) ~[spring-context-6.1.14.jar:6.1.14]
		at io.micrometer.observation.Observation.observe(Observation.java:499) ~[micrometer-observation-1.13.6.jar:1.13.6]
		at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124) ~[spring-context-6.1.14.jar:6.1.14]
		at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-6.1.14.jar:6.1.14]
		at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[?:?]
		at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:358) ~[?:?]
		at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java) ~[?:?]
		at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) ~[?:?]
		at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
		at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
		at java.base/java.lang.Thread.run(Thread.java:1570) [?:?]
Caused by: org.apache.hc.core5.http.ConnectionClosedException: Connection closed by peer
	at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onInput(AbstractHttp1StreamDuplexer.java:349) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.inputReady(AbstractHttp1IOEventHandler.java:64) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler.inputReady(ClientHttp1IOEventHandler.java:41) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.decryptData(SSLIOSession.java:633) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$200(SSLIOSession.java:74) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:202) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:142) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:178) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:127) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:86) ~[httpcore5-5.2.5.jar:5.2.5]
	at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44) ~[httpcore5-5.2.5.jar:5.2.5]
	at java.base/java.lang.Thread.run(Thread.java:1570) [?:?]

Could you provide any insights or guidance on resolving this?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing a large multipart upload through Service A and the Cloud Gateway, comparing the Spring Boot 3.3.2 and 3.3.5 behavior with Service B's reactive WebClient. Inspect the reported entry points TerminologyClient.kt:129-143 and CodeSystemImport.kt:76,131 alongside the Service A EOFException and Service B connection-closed logs. Done means the regression is isolated and large uploads complete successfully through the gateway path.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin, spring, spring-boot
Domain
api, backend, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.