Spring Boot REST API Timeout (with Examples)

REST API timeouts occur when an API takes longer to complete than expected or allowed in a Spring Boot application. Generally, timeouts are of two types i.e. connection timeouts and read timeouts.

  • Connection Timeout occurs when a client attempts to establish a connection with a server, but the server does not respond within a certain timeframe.
  • Read Timeout occurs when a connection is established, but the server takes too long to respond to a request.

We must handle these timeouts to ensure that a client does not wait indefinitely for a response. This article explores the timeout handling related to read timeouts on the server side as well as the client side.

1. Setting Up a Time Consuming API for Demo

Before going deep, let’s start with an API that intentionally wastes time to simulate a service method that takes more than 10 seconds to complete.

@Service
public class ItemService {

  public List<Item> getAll() {

    TimeoutUtils.busyOperation(10000);
    return List.of(new Item(1L, "Item-1"), new Item(2L, "Item-2"));
  }
}

We will call this service method from the REST controller handler methods and verify that APIs are timed out in configured 5 seconds, and do not wait for a complete 10 seconds.

2. Timeout a REST API with Spring MVC

Handling timeouts is not the responsibility of a client. It is also the responsibility of the server to respond in a finite (agreed-upon) time.

Spring MVC allows to run APIs in async mode that can be cut short when needed and send timeout errors to the client. It is a two-step process.

2.1. Set Request Timeout Property

We must set the spring.mvc.async.request-timeout property to ensure that Spring MVC-based REST APIs can timeout after the configurable amount of time.

The following property configuration sets the timeout of 5 seconds for asynchronous requests. If an asynchronous request takes longer than this configured timeout to complete, it will be terminated, and appropriate handling (e.g., a timeout error response) can be implemented.

spring.mvc.async.request-timeout=5000

2.2. Write Async REST API

Next, write an async REST API that executes the requested action in a separate thread and Spring manages its life cycle.

The following API uses the Callable return type, which allows for asynchronous processing of the request.

@GetMapping
public Callable<ResponseEntity<?>> getAll() {

  return () -> {
    try {
      return ResponseEntity.ok(itemService.getAll());
    } catch (Exception e) {
      throw new ApplicationException("Error while fetching all items from the database"); 
    }
  };
}

2.3. Demo

When we invoke the above API and the timeout has been reached, the response is returned immediately, and it even returns the 503 HTTP error.

3. Timeout a REST API with Resilience4j

Resilience4j is a lightweight fault tolerance library designed for functional programming with Circuit Breaker, Rate Limiter, Time Limiter, Retry and Bulkhead features. Its resilience4j-timelimiter module provides an in-memory TimeLimiterRegistry which we can use to manage (create and retrieve) TimeLimiter instances.

3.1. Maven

Start with adding the resilience4j to the Spring boot application. We are using the Spring Boot 3 dependencies.

<dependency>
  <groupId>io.github.resilience4j</groupId>
  <artifactId>resilience4j-spring-boot3</artifactId>
  <version>2.1.0</version>
</dependency>

3.2. Properties Configuration for Time Limiter

Next, we define the resilience4j.instances..timeoutDuration property to configure a TimeLimiter instance with configured timeout in seconds.

The following example configures a new time limiter instance with the name “itemService“. We can use this instance to any REST API to configure specific timeout duration.

resilience4j.instances.itemService.timeoutDuration=5s

3.3. Using @TimeLimiter in Handler Method

Next, we create an async REST API and annotate it with the @TimeLimiter annotation. In the name attribute of the annotation, we can specify the timelimiter instance configured in the properties file.

@GetMapping("timeLimiter")
@TimeLimiter(name = "itemService")
public CompletableFuture<ResponseEntity<?>> getAllWithTimeLimiter() {

  return CompletableFuture.supplyAsync(() -> {
    try {
      return ResponseEntity.ok(itemService.getAll());
    } catch (Exception e) {
      throw new ApplicationException("Error while fetching all items from the database"); 
    }
  });
}

3.4. Demo

We can now run the Spring boot application and verify the timeout settings for API.

4. Other Options to Handle Timeouts

Apart from handling timeouts using async REST APIs, we can use the following problem-specific options. They solve a specific problem and thus cannot be applied centrally. We generally need them after we identify a problem during troubleshooting.

4.1. Timeout a Long-Running Database Operation using @Transactional Annotation

In a Spring Data JPA application, @Transactional defines the scope of transactions in an application. When an annotated method is invoked, a new transaction is started. If the method executes without any exceptions, the transaction is committed when the method completes. If an exception is thrown, the transaction is rolled back, and any changes made during the transaction are undone.

The timeout attribute of @Transactional annotation specifies the maximum time, in seconds, that a transaction should be allowed to run before it’s automatically rolled back. This can be useful in situations where we want to limit the execution time of a transaction to prevent it from blocking resources or hanging indefinitely.

Typically, we annotate the service methods with @Transactional. These methods are responsible for
interacting with your database using Spring Data JPA repositories.

@Service
public class ItemService {

  @Transactional(timeout = 5) // Timeout is in seconds
  public List<Item> getAll() {

    return List.of(new Item(1L, "Item-1"), new Item(2L, "Item-2"));
  }
}

4.2. Timeout a Remote API Call with RestTemplate or WebClient

This type of timeout is required when we are fetching or aggregating data from remote APIs and returning to API clients. This ensures that the REST API remains responsive and doesn’t hang indefinitely.

In Spring applications, remote APIs are generally performed using RestTemplate (legacy) and WebClient (recommended). We can configure a RestTemplate bean with connection and read timeout settings, as follows:

@Configuration
public class RestTemplateConfig {

  @Bean
  public RestTemplate restTemplate() {

    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(5000); // 5 seconds
    factory.setReadTimeout(5000);   // 5 seconds
    return new RestTemplate(factory);
  }
}

Also, to implement timeouts with WebClient, we can configure it for specific calls or globally. For API-specific calls, we can use the Mono.timeout() or Flux.timeout() methods.

webClient.get()
  .uri("/items")
  .retrieve()
  .bodyToFlux(Item.class)
  .timeout(Duration.ofSeconds(5))
  .onErrorResume(ConnectTimeoutException.class, ex -> {
    //...
  })
  .subscribe(
     //...
  );

Similarly, we can configure the timeout globally at the underlying HTTP client library level. Each library has specific timeout configuration-related properties/methods, and we need to follow them. Spring provides built-in support for some HTTP client libraries, and the Reactor Netty is used by default.

The following code configures 5 seconds of read timeout and connection timeout for all outgoing remote requests.

@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {  
   
  @Bean
  public WebClient getWebClient() {

    HttpClient httpClient = HttpClient.create()
      .tcpConfiguration(client ->
          client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
          .doOnConnected(conn -> conn
              .addHandler(new ReadTimeoutHandler(5, TimeUnit.SECONDS))
              .addHandlerLast(new WriteTimeoutHandler(10, TimeUnit.SECONDS))));
     
    ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient.wiretap(true));     
 
    return WebClient.builder()
      .baseUrl("http://localhost:3000")
      .clientConnector(connector)
      .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
      .build();
  }
}

5. Conclusion

Proper timeout handling is very important in a Spring boot application, and it ensures that the application remains robust and responsive, even when dealing with potentially slow or unresponsive external services.

This Spring Boot tutorial discussed various timeout configurations on the server side with code examples.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.