HelloKoding

Practical coding guides

Spring Boot RestTemplate Timeout

When configuring RestTemplate timeout, there’re two settings that need to be considered, Connection and Read timeout. They can be configured by using RestTemplateBuilder in Spring Boot applications or SimpleClientHttpRequestFactory in Spring applications

Apart from that, you can connect to a non-routable IP address or an existing host with a blocked port to test a RestTemplate Connect timeout. As MockRestServiceServer overwrites RestTemplate Request factory settings, to test Read timeout, you can simulate a delayed backend controller with Thread.sleep and write an integration test in Spring Boot with @SpringBootTest

Let’s walk through this tutorial to explore in more detail

Connection vs Read Timeout

Connection timeout is used when opening a communications link to the remote resource. A java.net.SocketTimeoutException is thrown if the timeout expires before the connection can be established

Read timeout is used when reading from Input Stream when a connection is established to a remote resource. A java.net.SocketTimeoutException is also thrown if the timeout expires before there is data available for reading

Configure RestTemplate timeout

In Spring Boot, the connection and read timeout can be configured via RestTemplateBuilder

static final int TIMEOUT = 500;

@Bean
RestTemplate restTemplateWithConnectReadTimeout() {
    return new RestTemplateBuilder()
        .setConnectTimeout(Duration.ofMillis(TIMEOUT))
        .setReadTimeout(Duration.ofMillis(TIMEOUT))
        .build();
}

If your project doesn’t use Spring Boot, you can configure them via SimpleClientHttpRequestFactory

static final int TIMEOUT = 500;

@Bean
RestTemplate restTemplateTimeoutConfigWithRequestFactory() {
    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    requestFactory.setConnectTimeout(TIMEOUT);
    requestFactory.setReadTimeout(TIMEOUT);

    return new RestTemplate(requestFactory);
}

The default value of Connect and Read timeout is 0 specifies an infinite timeout

You can find the full configuration code as below

RestTemplateWithTimeoutConfig.java

package com.hellokoding.springcore;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;

@Configuration
public class RestTemplateWithTimeoutConfig {
    static final int TIMEOUT = 500;

    @Bean
    RestTemplate restTemplateWithConnectReadTimeout() {
        return new RestTemplateBuilder()
            .setConnectTimeout(Duration.ofMillis(TIMEOUT))
            .setReadTimeout(Duration.ofMillis(TIMEOUT))
            .build();
    }

    @Bean
    RestTemplate restTemplateWithConnectTimeout() {
        return new RestTemplateBuilder()
            .setConnectTimeout(Duration.ofMillis(TIMEOUT))
            .build();
    }

    @Bean
    RestTemplate restTemplateTimeoutWithRequestFactory() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(TIMEOUT);
        requestFactory.setReadTimeout(TIMEOUT);

        return new RestTemplate(requestFactory);
    }
}

Test a Connect timeout

You can test a RestTemplate Connect timeout setting by requesting to a non-routable IP address such as 10.255.255.255 or to an existing host but with a blocked port such as http://example.com:81

RestTemplateConnectTimeoutTest.java

package com.hellokoding.springcore;

import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.client.RestTemplate;

import java.net.SocketTimeoutException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = RestTemplateWithTimeoutConfig.class)
public class RestTemplateConnectTimeoutTest {
    @Autowired
    private RestTemplate restTemplateWithConnectTimeout;

    @ParameterizedTest
    @ValueSource(strings = {"http://example.com:81", "http://10.255.255.255"})
    public void testConnectTimeout(String url) {
        long startMillis = System.currentTimeMillis();

        Throwable throwable = catchThrowable(() -> {
            restTemplateWithConnectTimeout.getForObject(url, String.class);
        });

        long endMillis = System.currentTimeMillis();
        System.out.println("Execution time: " + (endMillis - startMillis));

        assertThat(throwable).hasRootCauseInstanceOf(SocketTimeoutException.class);
    }
}

Test a Read timeout

To test the RestTemplate Read timeout, you can create a test application to simulate a delayed backend API and use @SpringBootTest to create an integration test

@GetMapping("/test/delay")
public ResponseEntity delay(int millis) throws InterruptedException {
    Thread.sleep(millis);

    return ResponseEntity.ok().build();
}

Any requests to /test/delay will be delayed by Thread.sleep(millis) pauses the execution of the API. Full application file can be found as the following

RestTemplateReadTimeoutApplication.java

package com.hellokoding.springcore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class RestTemplateReadTimeoutApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestTemplateReadTimeoutApplication.class, args);
    }

    @GetMapping("/test/delay")
    public ResponseEntity delay(int millis) throws InterruptedException {
        Thread.sleep(millis);

        return ResponseEntity.ok().build();
    }
}

To create an integration test, use @SpringBootTest in conjunction with WebEnvironment.RANDOM_PORT and @LocalServerPort injected field to create a web test application context servlet based

@SpringBootTest(
    webEnvironment = WebEnvironment.RANDOM_PORT,
    classes = {RestTemplateWithTimeoutConfig.class, RestTemplateWithTimeoutTestApplication.class}
)

classes indicates the component classes to use for loading a Spring ApplicationContext

RestTemplateReadTimeoutTest.java

package com.hellokoding.springcore;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.web.client.RestTemplate;

import java.net.SocketTimeoutException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;

@SpringBootTest(
    webEnvironment = WebEnvironment.RANDOM_PORT,
    classes = {RestTemplateWithTimeoutConfig.class, RestTemplateReadTimeoutApplication.class}
)
public class RestTemplateReadTimeoutTest {
    @LocalServerPort
    private int port;

    @Autowired
    private RestTemplate restTemplateWithConnectTimeout;

    @Autowired
    private RestTemplate restTemplateWithConnectReadTimeout;

    @Test
    public void testReadTimeout() {
        long startMillis = System.currentTimeMillis();

        Throwable throwable = catchThrowable(() -> {
            String url = String.format("http://localhost:%d/test/delay?millis=%d", port, 600);
            restTemplateWithConnectReadTimeout.getForObject(url, String.class);
        });

        long endMillis = System.currentTimeMillis();
        System.out.println("Execution time: " + (endMillis - startMillis));

        assertThat(throwable).hasRootCauseInstanceOf(SocketTimeoutException.class);
    }

    @Test
    public void testReadTimeout2() {
        long startMillis = System.currentTimeMillis();

        Throwable throwable = catchThrowable(() -> {
            String url = String.format("http://localhost:%d/test/delay?millis=%d", port, 600);
            restTemplateWithConnectTimeout.getForObject(url, String.class);
        });

        long endMillis = System.currentTimeMillis();
        System.out.println("Execution time: " + (endMillis - startMillis));

        assertThat(throwable).hasRootCauseInstanceOf(SocketTimeoutException.class);
    }
}

Run the tests

You can run the test cases with your IDE or Maven

$ mvn -Dtest=RestTemplate*TimeoutTest test

RestTemplate*TimeoutTest with a wildcard character represents for both test classes RestTemplateConnectTimeoutTest and RestTemplateReadTimeoutTest

All test cases should be passed except testReadTimeout2 as it is using restTemplateWithConnectTimeout which only Connect timeout is configured

The execution time of RestTemplate requests in the test cases should be greater than the timeout configuration value

Conclusion

In this tutorial, we learned to configure RestTemplate connection and read timeout in Spring and Spring Boot applications

We also learned to test Connect timeout by connecting a RestTemplate to a non-routable IP address or an existing host with a blocked port, and learn to test Read timeout by simulating a delayed backend controller by using Thread.sleep and write an integration test in Spring Boot by using @SpringBootTest

You can find the full source code on GitHub

Follow HelloKoding