Resolving SocketTimeoutException Caused by Circular Dependency in Spring Cloud Microservices
The article analyzes a SocketTimeoutException occurring in a test environment due to a circular dependency between two Spring Cloud microservices, demonstrates how removing the circular call eliminates the timeout, and provides verification steps with code, JMeter load testing, and thread‑dump analysis.
Hello everyone, I'm Chen. Recently, after an iteration test, the test environment started showing many java.net.SocketTimeoutException: Read timed out errors after a while, and restarting the service only delayed the issue.
Note: Restarting services in a test environment is not a good practice because it may mask the real problem, which could later appear in production.
Preliminary Analysis
Log tracing revealed a circular dependency between microservices using HTTP calls:
Client calls Foo.hello()
Foo.hello() calls Boo.boo()
Boo.boo() calls back Foo.another()
The circular call was introduced in the latest iteration. Although the exact causal chain was unclear, removing the circular dependency stopped the SocketTimeoutException .
Investigating the Root Cause
To confirm that the circular call caused the timeout, the service containers were visualized. If all request‑handling threads in Foo are waiting for Boo.boo() while Boo needs to call back Foo.another() , no thread is left to serve Boo 's request, leading to a deadlock‑like situation when request rate exceeds processing capacity.
Verification
Key code snippets are provided below (full project at gitee.com/donghbcn/CircularDependency ).
Eureka Server
Start a simple Eureka server.
Service Foo
Spring Boot application that calls Boo via FeignClient. The Tomcat thread pool is limited to 16 threads to make the deadlock observable.
application.properties
spring.application.name=demo-foo
server.port=8000
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka
server.tomcat.threads.max=16 package com.cd.demofoo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FooController {
@Autowired
BooFeignClient booFeignClient;
@RequestMapping("/hello")
public String hello(){
long start = System.currentTimeMillis();
System.out.println("[" + Thread.currentThread() + "] foo:hello called, call boo:boo now");
booFeignClient.boo();
System.out.println("[" + Thread.currentThread() + "] foo:hello called, call boo:boo, total cost:" + (System.currentTimeMillis() - start));
return "hello world";
}
@RequestMapping("/another")
public String another(){
long start = System.currentTimeMillis();
try {
// simulate a time‑consuming call
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("foo:another called, total cost:" + (System.currentTimeMillis() - start));
return "another";
}
}Service Boo
Spring Boot application that calls back Foo.another() via FeignClient.
package com.cd.demoboo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BooController {
@Autowired
FooFeignClient fooFeignClient;
@RequestMapping("/boo")
public String boo(){
long start = System.currentTimeMillis();
fooFeignClient.another();
System.out.println("boo:boo called, call foo:another, total cost:" + (System.currentTimeMillis() - start));
return "boo";
}
}JMeter Load Test
A JMeter test with 30 threads in an infinite loop quickly saturates Foo 's thread pool, causing its logs to freeze and Boo to start throwing SocketTimeoutException . Screenshots are included.
Thread Dump (jstack)
The thread dump shows all Foo threads blocked inside hello() , confirming the deadlock scenario.
Conclusion
Circular dependencies between microservices are analogous to class‑level circular dependencies and can lead to severe problems such as deadlocks and tightly coupled services, effectively turning a microservice architecture into a "distributed monolith". Avoid creating circular calls to preserve service independence and reliability.
Microservices must not form circular calls; they easily cause deadlock states.
Strong coupling violates the microservice principle and can evolve the system into a distributed monolith.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.