Spring RestTemplate 🟢 RestTemplate Overview 🟢 RestTemplate Synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries such as the JDK 🟠HttpURLConnection, Apache HttpComponents, and others. RestTemplate offers templates for common scenarios by HTTP method, in addition to the generalized exchange and execute methods that support less frequent cases. You can create 🟢 RestTemplate from 🟢 RestTemplateBuilder: private final RestTemplateBuilder restTemplateBuilder; public Page<ResourceDTO> listResources() { RestTemplate restTemplate = restTemplateBuilder.build(); // ... } HTTP GET response as JSON string Next, you can retrieve an entity by doing a GET on the specified URL. The response is converted and stored in a 🟢 ResponseEntity<T>: Print JSON response ResponseEntity<String> stringResponse = restTemplate.getForEntity("http://localhost:8080/api/v1/resource", String.class); System.out.println(stringResponse.getBody()); HTTP GET response as ⚪ Map<K,V> ⚪ Map<K,V> is a very handy way to get information about what actually comes back in the response: ResponseEntity<Map> mapResponse = restTemplate.getForEntity("http://localhost:8080/api/v1/resource", Map.class); Under the hood, Spring invokes Jackson to parse the returned JSON into ⚪ Map<K,V>. HTTP GET response as 🟠JsonNode To get better capabilities than just a ⚪ Map<K,V>, you can get the response as 🟠JsonNode: ResponseEntity<JsonNode> jsonResponse = restTemplate.getForEntity("http://localhost:8080/api/v1/resource", JsonNode.class); With this, you can for example print name of all resources: jsonResponse.getBody() .findPath("content") .elements() .forEachRemaining(node -> { System.out.println(node.get("name").asText()); }); Jackson gives you a lot of flexibility when you’re working with JSON string mapped to Java objects. HTTP GET pageable response with custom Java Object First, implement 🟢 ResourceDTOPageImpl<T> extending 🟢 PageImpl<T> and annotate it with Jackson annotations: A page is a sublist of a list of objects. It allows gain information about the position of it in the containing entire list. @JsonIgnoreProperties(ignoreUnknown = true, value = "pageable") (1) public class ResourceDTOPageImpl extends PageImpl<ResourceDTO> { @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) (2) public ResourceDTOPageImpl(@JsonProperty("content") List<ResourceDTO> content, (3) @JsonProperty("number") int page, (3) @JsonProperty("size") int size, (3) @JsonProperty("totalElements") long total) { (3) super(content, PageRequest.of(page, size), total); } public ResourceDTOPageImpl(List<ResourceDTO> content, Pageable pageable, long total) { super(content, pageable, total); } public ResourceDTOPageImpl(List<ResourceDTO> content) { super(content); } } 1 Ignore pageable property 2 Tell Jackson to use this constructor where we bind properties to parameters 3 The actual bindings Next, you can use it like: ResponseEntity<ResourceDTOPageImpl> pageableResponse = restTemplate.getForEntity("http://localhost:8080/api/v1/resource", ResourceDTOPageImpl.class); Externalize Base URI You can externalize baseUri (e.g. http://localhost:8080) by implementing 🟢 RestTemplateBuilderConfig: @Configuration (1) public class RestTemplateBuilderConfig { @Value("${rest.template.baseUri}") (2) String baseUri; @Bean RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) { assert StringUtils.hasText(baseUri); (3) return configurer.configure(new RestTemplateBuilder()); (4) .uriTemplateHandler(new DefaultUriBuilderFactory(baseUri)); (5) } } 1 @Configuration indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime 2 @Value indicates a default value expression for the annotated element 3 Once baseUri is not set, the bean will fail to instantiate 4 Configures new 🟢 RestTemplateBuilder instance with Spring Boot defaults using 🔴 RestTemplateBuilderConfigurer 5 Configures baseUri in 🟢 RestTemplateBuilder using 🟢 DefaultUriBuilderFactory This way, you can set the property in application.properties and maintain it externally: rest.template.baseUri=http://localhost:8080 and use it like: ResponseEntity<ResourceDTOPageImpl> response = restTemplate.getForEntity("/api/v1/resource", ResourceDTOPageImpl.class); Query Parameters You can add query parameters to the request by using 🟢 UriComponentsBuilder: UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath("/api/v1/resource"); uriComponentsBuilder.queryParam("name", name); ResponseEntity<ResourceDTOPageImpl> response = restTemplate.getForEntity(uriComponentsBuilder.toUriString(), ResourceDTOPageImpl.class); URI Parameters You can pass URI parameters like this: ResourceDTO resourceDTO = restTemplate.getForObject("/api/v1/resource/{resourceId}", ResourceDTO.class, id); HTTP POST You can make an HTTP POST request like this: URI uri = restTemplate.postForLocation("/api/v1/resource", newResourceDTO); (1) ResourceDTO resourceDTO = restTemplate.getForObject(uri.getPath(), ResourceDTO.class); (2) 1 New resource is created and uri to it is returned 2 Another request returns the created resource The below will return 🟢 ResponseEntity<T> with body = null (so with null 🟢 ResourceDTO): ResponseEntity<ResourceDTO> response = restTemplate.postForEntity("/api/v1/resource", newResourceDTO, ResourceDTO.class); HTTP PUT You can make an HTTP PUT request like this: restTemplate.put("/api/v1/resource/{resourceId}", resourceDTO, resourceDTO.getId()); ResourceDTO updatedResourceDTO = restTemplate.getForObject("/api/v1/resource/{resourceId}", ResourceDTO.class, resourceDTO.getId()); 🟢 RestTemplate#put(String url, Object request, Object… uriVariables) method does not return anything. If you want to get the created/updated resource, you need to make HTTP GET request. HTTP DELETE You can make an HTTP DELETE request like this: restTemplate.delete("/api/v1/resource/{resourceId}", id); Testing 🟢 RestTemplate HTTP GET Example Test: @RestClientTest (1) @Import(RestTemplateBuilderConfig.class) (2) public class ResourceClientMockTest { @Autowired ObjectMapper objectMapper; (3) @Autowired RestTemplateBuilder restTemplateBuilder; (4) MockRestServiceServer mockServer; @Mock RestTemplateBuilder mockRestTemplateBuilder = new RestTemplateBuilder(new MockServerRestTemplateCustomizer()); (5) ResourceClient resourceClient; @BeforeEach void setUp() { RestTemplate restTemplate = restTemplateBuilder.build(); (6) mockServer = MockRestServiceServer.bindTo(restTemplate).build(); (7) when(mockRestTemplateBuilder.build()).thenReturn(restTemplate); (8) resourceClient = new ResourceClientImpl(mockRestTemplateBuilder); (9) } @Test void testListResources() throws JsonProcessingException { String payload = objectMapper.writeValueAsString(getPage()); mockServer.expect(method(HttpMethod.GET)) (10) .andExpect(requestTo("http://localhost:8080/api/v1/resource")) .andRespond(withSuccess(payload, MediaType.APPLICATION_JSON)); Page<ResourceDTO> resourceDTOs = resourceClient.listResources(); (11) assertThat(resourceDTOs.getContent().size()).isGreaterThan(0); } // ... } 1 @RestClientTest annotates test that focuses only on beans that use 🟢 RestTemplateBuilder or ⚪ RestClient.Builder. 2 @Import indicates one or more component classes to import - typically @Configuration classes. 🟢 RestTemplateBuilderConfig is imported, so 🟢 RestTemplate can use externalized Base URI 3 🟢 ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects), or to and from a general-purpose JSON Tree Model (🟠JsonNode), as well as related functionality for performing conversions. 4 🟢 RestTemplateBuilder is injected by Spring. 5 🟢 RestTemplateBuilder is also mocked with 🔴 MockRestServiceServer support thanks to 🟢 MockServerRestTemplateCustomizer. 6 Before each test we create 🟢 RestTemplate using 🟢 RestTemplateBuilder injected by Spring. 7 Next, we create 🔴 MockRestServiceServer with 🟢 RestTemplate binding. We need this binding, so the request sent by 🟢 RestTemplate can be handled by 🔴 MockRestServiceServer. 8 Then, we stub mocked 🟢 RestTemplateBuilder#build() method to return 🟢 RestTemplate 9 At the end of the test configuration we pass mocked 🟢 RestTemplateBuilder to the 🟢 ResourceClientImpl constructor, which finishes setup of the mock. 10 During the test, we can set up 🔴 MockRestServiceServer behavior. 11 Request sent by 🟢 ResourceClientImpl via 🟢 RestTemplate returned by mocked 🟢 RestTemplateBuilder will return response from 🔴 MockRestServiceServer successfully. Query Parameters You can set up 🔴 MockRestServiceServer to respond on the request to url with query parameters in the following way: URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/api/v1/resource").queryParam("name", "Some Name").build().toUri(); mockServer.expect(method(HttpMethod.GET)) .andExpect(requestTo(uri)) .andExpect(queryParam("name", "Some Name")) .andRespond(withSuccess(response, MediaType.APPLICATION_JSON)); URI Parameters You can set up 🔴 MockRestServiceServer to respond on the request to url with URI parameters in the following way: mockServer.expect(method(HttpMethod.GET)) .andExpect(requestToUriTemplate("http://localhost:8080/api/v1/resource/{resourceId}", resourceDTO.getId())) .andRespond(withSuccess(payload, MediaType.APPLICATION_JSON)); HTTP POST You can set up 🔴 MockRestServiceServer to respond on the HTTP POST request like this: URI uri = UriComponentsBuilder.fromPath("/api/v1/resource/{resourceId}").build(resourceDTO.getId()); mockServer.expect(method(HttpMethod.POST)) .andExpect(requestTo("http://localhost:8080/api/v1/resource")) .andRespond(withAccepted().location(uri)); HTTP PUT You can set up 🔴 MockRestServiceServer to respond on the HTTP PUT request like this: mockServer.expect(method(HttpMethod.PUT)) .andExpect(requestToUriTemplate("http://localhost:8080/api/v1/resource/{resourceId}", resourceDTO.getId())) .andRespond(withNoContent()); HTTP DELETE You can set up 🔴 MockRestServiceServer to respond on the HTTP DELETE request like this: mockServer.expect(method(HttpMethod.DELETE)) .andExpect(requestToUriTemplate("http://localhost:8080/api/v1/resource/{resourceId}", resourceDTO.getId())) .andRespond(withNoContent()); The HTTP DELETE does not return any entity. To make sure that 🔴 MockRestServiceServer handled the expected request, you can call 🔴 MockRestServiceServer#verify(). Response Status Code: 404 Not Found Example test how to verify 404 Response Status Code: @Test void testDeleteNotFound() { mockServer.expect(method(HttpMethod.DELETE)) .andExpect(requestToUriTemplate("http://localhost:8080/api/v1/resource/{resourceId}", resourceDTO.getId())) .andRespond(withResourceNotFound()); assertThrows(HttpClientErrorException.class, () -> { resourceClient.deleteResource(resourceDTO.getId()); }); mockServer.verify(); }