49

I have a Spring Boot REST service that sometimes call third party services as a part of a request. I would like to set a timeout on all my resources (let's say 5 seconds), so that if any request handling (the whole chain, from incoming to response) takes longer than 5 seconds my controllers responds with HTTP 503 instead of the actual response. It would be awesome if this was just a Spring property, for example setting

spring.mvc.async.request-timeout=5000

but I haven't had any luck with that. I've also tried extending WebMvcConfigurationSupport and overriding configureAsyncSupport:

@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
    configurer.setDefaultTimeout(5000);
    configurer.registerCallableInterceptors(timeoutInterceptor());
}

@Bean
public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
    return new TimeoutCallableProcessingInterceptor();
}

without any luck.

I suspect I have to manually time all my third party calls, and if they take too long, throw a timeout exception. Is that right? Or is there any easier, holistic solution that covers all my request endpoints?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Jesper N
  • 2,115
  • 4
  • 23
  • 32

9 Answers9

41

You need to return a Callable<> if you want spring.mvc.async.request-timeout=5000 to work.

@RequestMapping(method = RequestMethod.GET)
public Callable<String> getFoobar() throws InterruptedException {
    return new Callable<String>() {
        @Override
        public String call() throws Exception {
            Thread.sleep(8000); //this will cause a timeout
            return "foobar";
        }
    };
}
Ashok Goli
  • 5,043
  • 8
  • 38
  • 68
Cyril
  • 2,376
  • 16
  • 21
26

A fresh answer for Spring Boot 2.2 is required as server.connection-timeout=5000 is deprecated. Each server behaves differently, so server specific properties are recommended instead.

SpringBoot embeds Tomcat by default, if you haven't reconfigured it with Jetty or something else. Use server specific application properties like server.tomcat.connection-timeout or server.jetty.idle-timeout.

As commented by Wilkinson:

Setting the connection timeout will only result in a timeout when the client connects but is then too slow to send its request. If you want the client to wait for a maximum of 30 seconds for a response you will have to configure that on the client-side. If you want the server-side to only spend a maximum of 30 seconds handling the request there is no way to guarantee that as you cannot force the thread that is handling the request to stop.

You can also try setting spring.mvc.async.request-timeout

Zon
  • 18,610
  • 7
  • 91
  • 99
  • 3
    I used all 3 of them for an application that uses `@RestController` and I set very low values for each of them, but they are all ignored. Even if the request takes longer, the page is normally served. – ROMANIA_engineer Aug 19 '21 at 21:08
  • This does not set a limit on your answer but on how much time it takes to the clinet to send you the request after opening the connection: se here https://github.com/spring-projects/spring-boot/issues/21914 – user1708042 Sep 15 '21 at 06:57
  • so connection/idle timeout is actually request timeout? )) – Simon Logic Dec 17 '21 at 05:49
23

The @Transactional annotation takes a timeout parameter where you can specify timeout in seconds for a specific method in the @RestController

@RequestMapping(value = "/method",
    method = RequestMethod.POST,
    produces = MediaType.APPLICATION_JSON_VALUE)
@Timed
@Transactional(timeout = 120)
Smile
  • 3,832
  • 3
  • 25
  • 39
fvorraa
  • 287
  • 3
  • 2
13

I would suggest you have a look at the Spring Cloud Netflix Hystrix starter to handle potentially unreliable/slow remote calls. It implements the Circuit Breaker pattern, that is intended for precisely this sorta thing.

See offcial docs for more information.

demaniak
  • 3,716
  • 1
  • 29
  • 34
  • 4
    I don't get why this was downvoted, it looks to me that implementing the circuit breaker pattern is the best answer. Otherwise, by simply returning a 503 while leaving the request running, you probably waste resources – Jeremie Apr 20 '17 at 15:32
  • 6
    You could also use resilience4j https://resilience4j.readme.io/docs/circuitbreaker. Actual Netflix Hystrix is no longer maintained. – Liquidpie Feb 27 '20 at 16:42
7

You can try server.connection-timeout=5000 in your application.properties. From the official documentation:

server.connection-timeout= # Time in milliseconds that connectors will wait for another HTTP request before closing the connection. When not set, the connector's container-specific default will be used. Use a value of -1 to indicate no (i.e. infinite) timeout.

On the other hand, you may want to handle timeouts on the client side using Circuit Breaker pattern as I have already described in my answer here: https://stackoverflow.com/a/44484579/2328781

Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
  • 3
    `server.connection-timeout=5000` - Deprecated - Each server behaves differently. Use server specific properties instead. – Zon Apr 15 '20 at 06:59
  • @Zon do you know the server specific properties for the basic spring boot 2 application? – MozenRath May 13 '20 at 10:26
  • @MozenRath, SpringBoot embeds Tomcat by default, if you haven't reconfigured it with Jetty or something else. Use server specific application properties like `server.tomcat.connection-timeout` or `server.jetty.idle-timeout` instead. – Zon May 13 '20 at 18:42
6

if you are using RestTemplate than you should use following code to implement timeouts

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate(clientHttpRequestFactory());
}

private ClientHttpRequestFactory clientHttpRequestFactory() {
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setReadTimeout(2000);
    factory.setConnectTimeout(2000);
    return factory;
}}

The xml configuration

<bean class="org.springframework.web.client.RestTemplate">
<constructor-arg>
    <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"
        p:readTimeout="2000"
        p:connectTimeout="2000" />
</constructor-arg>

Pankaj Pandey
  • 1,015
  • 6
  • 10
2

In Spring properties files, you can't just specify a number for this property. You also need to specify a unit. So you can say spring.mvc.async.request-timeout=5000ms or spring.mvc.async.request-timeout=5s, both of which will give you a 5-second timeout.

MiguelMunoz
  • 4,548
  • 3
  • 34
  • 51
  • Could you elaborate? I tried this, and it wouldn't work without a unit. – MiguelMunoz Nov 05 '18 at 18:48
  • Have a look at some of the others answers or the official documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html). The question is related to SpringBoot. – unwichtich Nov 06 '18 at 02:49
  • Your link just took me to the source code, which told me that the property type is a java.time.Duration, which requires a unit. I did look at the other answers, and none of them discussed how to specify `spring.mvc.async.request-timeout` property in a properties file. They all specified it in code. As far as I can tell, the documentation you linked to supports what I said. So once again. Could you please elaborate? – MiguelMunoz Nov 07 '18 at 21:11
  • See the answer of Danylo Zatorsky. Or any other in the internet: https://stackoverflow.com/questions/47025525/set-timeout-for-specific-async-request-in-spring-boot or https://stackoverflow.com/questions/51006913/increasing-connection-timeout-for-tomcat-in-spring-boot – unwichtich Nov 08 '18 at 12:46
  • 1
    So are you saying that I'm talking about the wrong property? My point was that, in my experience, properties that return a Duration need to have units specified. I just used the `spring.mvc.async.request-timeout` property as an example because the original questioner used it. Are you taking issue with that claim? Or are you just referring to the correct property to use. Please just answer my question yourself instead of pointing me elsewhere. Your answers have been very vague until this latest one, which finally gives me something more specific. – MiguelMunoz Nov 08 '18 at 19:20
  • Setting it to -1 fixed it for me in the end. That AND returning a `Callable>>` – ndtreviv Jul 13 '20 at 21:16
  • Setting what to -1? Are you referring to the `spring.mvc.async.request-timeout` property? Or a different one? – MiguelMunoz Jul 19 '20 at 10:33
2

I feel like none of the answers really solve the issue. I think you need to tell the embedded server of Spring Boot what should be the maximum time to process a request. How exactly we do that is dependent on the type of the embedded server used.

In case of Undertow, one can do this:

@Component
class WebServerCustomizer : WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
    override fun customize(factory: UndertowServletWebServerFactory) {
        factory.addBuilderCustomizers(UndertowBuilderCustomizer {
            it.setSocketOption(Options.READ_TIMEOUT, 5000)
            it.setSocketOption(Options.WRITE_TIMEOUT, 25000)
        })
    }
}

Spring Boot official doc: https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/howto.html#howto-configure-webserver

geliba187
  • 357
  • 2
  • 11
1

You can configure the Async thread executor for your Springboot REST services. The setKeepAliveSeconds() should consider the execution time for the requests chain. Set the ThreadPoolExecutor's keep-alive seconds. Default is 60. This setting can be modified at runtime, for example through JMX.

@Bean(name="asyncExec")
public Executor asyncExecutor()
{
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(3);
    executor.setMaxPoolSize(3);
    executor.setQueueCapacity(10);
    executor.setThreadNamePrefix("AsynchThread-");
    executor.setAllowCoreThreadTimeOut(true);
    executor.setKeepAliveSeconds(10);
    executor.initialize();

    return executor;
}

Then you can define your REST endpoint as follows

@Async("asyncExec")
@PostMapping("/delayedService")
public CompletableFuture<String> doDelay()
{ 
    String response = service.callDelayedService();
    return CompletableFuture.completedFuture(response);
}
Komoo
  • 301
  • 3
  • 8