Backend Development 8 min read

Analyzing and Resolving Circular Dependency Causing SocketTimeoutException in SpringBoot Microservices

The article investigates a SocketTimeoutException caused by a circular dependency between two SpringBoot microservices, explains how the deadlock occurs, demonstrates a reproducible test setup with Eureka, FeignClient and JMeter, and provides a concrete code‑level fix to break the loop.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Analyzing and Resolving Circular Dependency Causing SocketTimeoutException in SpringBoot Microservices

During a recent integration test a Java microservice system began throwing numerous java.net.SocketTimeoutException: Read timed out errors after a short period of stable operation. Restarting the services only postponed the issue, indicating a deeper problem in the service interaction.

The logs revealed a circular call chain: the client calls Foo.hello() , which invokes Boo.boo() ; Boo.boo() in turn calls Foo.another() . This creates a loop where each service waits for the other, effectively causing a deadlock when all request‑handling threads become blocked.

To verify the hypothesis, a minimal reproducible environment was built:

Eureka server for service discovery.

Service Foo (SpringBoot) exposing /hello and /another , calling Boo via a Feign client. Tomcat thread pool limited to 16 threads.

Service Boo (SpringBoot) exposing /boo , calling Foo.another() via a Feign client.

JMeter test plan with 30 concurrent threads continuously invoking the /hello endpoint.

Running the test quickly saturated the thread pool of Foo ; all threads were stuck in the hello() call waiting for Boo.boo() , while Boo was simultaneously waiting for Foo.another() . The resulting stack traces (captured with jstack ) showed every thread blocked in the circular call, confirming a deadlock.

Removing the circular dependency broke the deadlock. The fix involved eliminating the call from Foo.another() back to Boo (or vice‑versa), after which the SocketTimeoutException disappeared and the services handled load normally.

Key take‑aways:

Microservices must not form circular call relationships; such loops can quickly lead to thread starvation and time‑outs.

Even with lightweight query operations, a circular dependency can create a distributed monolith, violating microservice principles of loose coupling.

Limiting thread pool sizes in containers can make the deadlock observable during load testing.

For reference, the full source code is available at https://gitee.com/donghbcn/CircularDependency .

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 delay
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("foo:another called, total cost:" + (System.currentTimeMillis() - start));
        return "another";
    }
}
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";
    }
}
backendMicroservicesSpring BootEurekaCircular Dependencysocket timeoutFeignClient
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.