Skip to content

Random CancellationException instead of ResourceAccessException after upgrading to Spring Boot 3.4.0 #33973

@SimonPNorra

Description

@SimonPNorra

Hi Spring-Team,

After upgrading to Spring Boot 3.4.0, we have encountered an issue with RestTemplate where random CancellationExceptions are thrown instead of the expected ResourceAccessException during timeout scenarios.

Observations:
This behavior is inconsistent; sometimes a CancellationException is thrown, and other times a ResourceAccessException is thrown.
This issue did not occur in previous versions of Spring Boot.

Reproduction:
We have created a test case to demonstrate the issue: https://github.com/SimonPNorra/timeout-execption-case

Steps to Reproduce:
Run the test case included in the project and observe the exceptions thrown. You will notice either a CancellationException or a ResourceAccessException occurring randomly.

Expected Behavior:
Consistent behavior with ResourceAccessException thrown during timeout scenarios.

Actual Behavior:
Random exceptions are thrown: either CancellationException or ResourceAccessException.

Please let us know if further information is required.

Thank you.

Regards

Activity

wilkinsona

wilkinsona commented on Nov 26, 2024

@wilkinsona
Member

Thanks for the sample.

Unfortunately, on my machine, it doesn't reproduce the behavior that you've described. I've tried with both Java 21 and Java 23 and the exception was always a ResourceAccessException. I'm on macOS 14.7.1.

Can you please provide some more information about the OS you're running on and the version and distribution of Java that you're using? Also, please share the full stack trace of both the CancellationException and the ResourceAccessException that you're seeing.

SimonPNorra

SimonPNorra commented on Nov 27, 2024

@SimonPNorra
Author

Hey @wilkinsona

Thank you for looking into this issue. Here is the information about my local machine, which I used to run the example:

Machine Details:

Device: MacBook Pro
Processor: Apple M1 Pro
Operating System: macOS 15.1.1 (24B91)
JDK: azul-21.0.2 (aarch64)

Stack Traces from the Example:
(Below are both stack traces as observed from running the test case.)

java.util.concurrent.CancellationException
	at java.base/java.util.concurrent.CompletableFuture.cancel(CompletableFuture.java:2510)
	at java.net.http/jdk.internal.net.http.common.MinimalFuture.cancel(MinimalFuture.java:109)
	at org.springframework.http.client.JdkClientHttpRequest$TimeoutHandler.lambda$new$0(JdkClientHttpRequest.java:234)
	at java.base/java.util.concurrent.CompletableFuture$UniRun.tryFire(CompletableFuture.java:787)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2179)
	at java.base/java.util.concurrent.CompletableFuture$DelayedCompleter.run(CompletableFuture.java:2931)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
	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:1583)
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:49111/test": Request timed out
	at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:926)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:906)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
	at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:442)
	at com.example.timeoutexecptioncase.TimeoutExecptionCaseApplicationTests.contextLoads(TimeoutExecptionCaseApplicationTests.java:35)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85)
	at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.net.http.HttpTimeoutException: Request timed out
	at org.springframework.http.client.JdkClientHttpRequest.executeInternal(JdkClientHttpRequest.java:124)
	at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
	... 71 more

In addition, this issue first appeared in our application running as a GCP CloudRun service. The service operates using the following base image: FROM gcr.io/distroless/java21-debian12

Let me know if you need more details about the environment!

wilkinsona

wilkinsona commented on Nov 27, 2024

@wilkinsona
Member

Thanks for the additional details and stack traces. The change in behavior that you've seen upon upgrading to Spring Boot 3.4 is due to this change in Spring Framework. We'll transfer this issue to the Framework team so that they can continue the investigation.

SimonPNorra

SimonPNorra commented on Nov 27, 2024

@SimonPNorra
Author

Hi there,

I’ve updated my example project to include additional test scenarios:

  1. Unit Test (as included previously)
  2. Test from within the Spring application, which can be executed locally
  3. Test using a distroless Docker image based on gcr.io/distroless/java21-debian12, replicating the execution environment where this issue was originally observed

I hope these additions help with your investigation.

Thank you for your support!

aldex32

aldex32 commented on Jan 31, 2025

@aldex32

I am having the same issue as @SimonPNorra

added
in: webIssues in web modules (web, webmvc, webflux, websocket)
on Feb 3, 2025
jaceko

jaceko commented on Mar 20, 2025

@jaceko

I'm having the same issue on Linux (JDK 21, Spring boot 3.4.3)

11 remaining items

self-assigned this
on Aug 1, 2025
added this to the 6.2.10 milestone on Aug 1, 2025
rstoyanchev

rstoyanchev commented on Aug 1, 2025

@rstoyanchev
Contributor

In the TimeoutHandler we call CompletableFuture#cancel and the Javadoc for that says:

If not already completed, completes this CompletableFuture with a CancellationException.

However, in some cases it is wrapped in ExecutionException and others it is not. Below is a stracktrace from when it is wrapped, and the cause is identical to when it is not wrapped as shown in comments above.

java.util.concurrent.ExecutionException: java.util.concurrent.CancellationException: Request cancelled
	at java.base/java.util.concurrent.CompletableFuture.wrapInExecutionException(CompletableFuture.java:345)
	at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:440)
	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2094)
	at org.springframework.http.client.JdkClientHttpRequest.executeInternal(JdkClientHttpRequest.java:106)
	at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:71)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:801)
	...
Caused by: java.util.concurrent.CancellationException: Request cancelled
	at java.net.http/jdk.internal.net.http.MultiExchange.cancel(MultiExchange.java:278)
	at java.net.http/jdk.internal.net.http.MultiExchange$CancelableRef.cancel(MultiExchange.java:187)
	at java.net.http/jdk.internal.net.http.common.MinimalFuture.cancel(MinimalFuture.java:107)
	at org.springframework.http.client.JdkClientHttpRequest$TimeoutHandler.lambda$new$0(JdkClientHttpRequest.java:234)
	at java.base/java.util.concurrent.CompletableFuture$UniRun.tryFire$$$capture(CompletableFuture.java:808)
	at java.base/java.util.concurrent.CompletableFuture$UniRun.tryFire(CompletableFuture.java)

Currently we only handle the wrapped case, but should also handle it not wrapped.

added a commit that references this issue on Aug 2, 2025
0d2c3e4
rstoyanchev

rstoyanchev commented on Aug 8, 2025

@rstoyanchev
Contributor

Superseded by #34721.

removed this from the 6.2.10 milestone on Aug 8, 2025
added a commit that references this issue on Aug 8, 2025
7a55ce4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)status: supersededAn issue that has been superseded by anothertype: bugA general bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Participants

    @rstoyanchev@wilkinsona@jaceko@SimonPNorra@spring-projects-issues

    Issue actions

      Random CancellationException instead of ResourceAccessException after upgrading to Spring Boot 3.4.0 · Issue #33973 · spring-projects/spring-framework