Course – LS (cat=REST)

Get started with Spring and Spring Boot, through the reference Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll look into RestTemplate to invoke a RESTful endpoint and read the response of type Page<Entity>. We’ll take a quick look at how Jackson deserializes the JSON response received by RestTemplate. We’ll set up a simple RESTful endpoint using employee data.

Further along, we’ll set up a client class that will use RestTemplate to consume data from the endpoint, first leading to an exception. Then we’ll take the necessary steps to enable the RestTemplate client to read the JSON response successfully. Finally, we’ll write an integration test to verify the correct behavior.

2. RestTemplate and Jackson Deserialization

RestTemplate is a widely used client-side HTTP communication library that simplifies the process of making HTTP requests and handling responses. When we make an HTTP call to the server using RestTemplate, the response from the server is typically in JSON format. Jackson is responsible for deserializing this JSON response into Java objects.

When Jackson encounters a JSON object and needs to create a corresponding Java class instance, it looks for a suitable constructor or factory method to invoke. By default, Jackson uses the default constructor for instantiation. However, in some cases, the default constructor may not be available or may not be sufficient to initialize the object properly.

To address such cases, the @JsonCreator annotation can be used to mark a constructor or factory method that Jackson should use for instantiation. This allows us to define custom logic for object creation during deserialization.

Additionally, when we want Jackson to deserialize JSON while capturing the generic type, we can provide the instance of ParameterizedTypeReference. The purpose of this class is to enable capturing and passing a generic type.

To capture the generic type and retain it at runtime, we need to create a subclass, mostly inline using new ParameterizedTypeReference<List<String>>() {}. The resulting instance can then be used to obtain a Type instance that carries the captured parameterized type information at runtime.

Next, let’s set up a simple example of employee data containing a RESTful endpoint and a client class that calls the endpoint

3. Defining a REST Controller

Let’s set up a simple example of employee data. We’ll create a GET /employee/data endpoint that returns EmployeeDto data as a paged response:

@GetMapping("/data")
public ResponseEntity<Page<EmployeeDto>> getData(@RequestParam(defaultValue = "0") int page, 
  @RequestParam(defaultValue = "10") int size) {
    List<EmployeeDto> empList = listImplementation();

    int totalSize = empList.size();
    int startIndex = page * size;
    int endIndex = Math.min(startIndex + size, totalSize);

    List<EmployeeDto> pageContent = empList.subList(startIndex, endIndex);

    Page<EmployeeDto> employeeDtos = new PageImpl<>(pageContent, PageRequest.of(page, size), totalSize);

    return ResponseEntity.ok().body(employeeDtos);
}

Clearly, we see that the getData() method is returning the Page<EmplyeeDto> as a response with the List<EmployeeDto> as content.

4. Defining a Client With RestTemplate

Let’s consider a typical scenario where we want to call the GET /organization/data endpoint over HTTP from another external service. Let’s define the client that will call the endpoint using RestTemplate. It’ll then try to deserialize the JSON into Page<EmployeeDto>.

For Jackson to deserialize the data from JSON to Page<EmployeeDto>, we’ll provide the concrete implementation class PageImpl of the abstract Page interface:

@Component
public class EmployeeClient {
    private final RestTemplate restTemplate;

    public EmployeeClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public Page<EmployeeDto> getEmployeeDataFromExternalAPI(Pageable pageable) {
        String url = "http://localhost:8080/employee";

        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(url)
          .queryParam("page", pageable.getPageNumber())
          .queryParam("size", pageable.getPageSize());

        ResponseEntity<PageImpl<EmployeeDto>> responseEntity = restTemplate.exchange(uriBuilder.toUriString(),
          HttpMethod.GET, null, new ParameterizedTypeReference<PageImpl<EmployeeDto>>() {
          });

        return responseEntity.getBody();
    }
}

 However, an attempt to provide Jackson with the ParameterizedType<Page<EmployeeDto>> or ParameterizedType<PageImpl<EmployeeDto>> would lead to an error:

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Pageable]; 
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 160] (through reference chain: org.springframework.data.domain.PageImpl["pageable"])

 5. How to Solve HttpMessageConversionException

We’ve seen that when RestTemplate calls the endpoint that returns Page<EmployeeDto>, the response cannot be read successfully into PageImpl<EmployeeDto>. This is because the PageImpl class has no default constructor. Additionally, there’s no @JsonCreator annotation in any of the existing constructors.

To fix the deserialization issue, let’s define a custom class that extends PageImpl and has the default constructor as well as the @JsonCreator annotation:

public class CustomPageImpl<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public CustomPageImpl(@JsonProperty("content") List<T> content, @JsonProperty("number") int number,
      @JsonProperty("size") int size, @JsonProperty("totalElements") Long totalElements,
      @JsonProperty("pageable") JsonNode pageable, @JsonProperty("last") boolean last,
      @JsonProperty("totalPages") int totalPages, @JsonProperty("sort") JsonNode sort,
      @JsonProperty("numberOfElements") int numberOfElements) {
        super(content, PageRequest.of(number, 1), 10);
    }

    public CustomPageImpl(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public CustomPageImpl(List<T> content) {
        super(content);
    }

    public CustomPageImpl() {
        super(new ArrayList<>());
    }
}

Essentially, the CustomPageImpl class provides the custom constructors that can be used for deserializing JSON responses into instances of the class. It’s extending the PageImpl class, which is commonly used for representing paginated data. Also, we added the annotation @JsonCreator(JsonCreator.Mode.PROPERTIES) to specify that the constructor that follows should be used for deserialization.

Next, let’s refactor the client so that restTemplate.exchange() converts the JSON response into CustomPageImpl:

ResponseEntity<CustomPageImpl<EmployeeDto>> responseEntity = restTemplate.exchange(
  uriBuilder.toUriString(),
  HttpMethod.GET,
  null,
  new ParameterizedTypeReference<CustomPageImpl<EmployeeDto>>() {}
);

Here, the restTemplate.exchange() method is invoked to send an HTTP GET request. It expects a response of type ResponseEntity<CustomPageImpl<EmployeeDto>>.

The ParameterizedTypeReference<CustomPageImpl<EmployeeDto>> handles the response type, allowing the deserialization of the response body into a CustomPageImpl containing EmployeeDto objects. This is necessary because the generic type information is lost at runtime due to Java’s type erasure.

 6. Integration Test

Finally, let’s test that the client works as expected using the CustomPageImpl:

@Test
void givenGetData_whenRestTemplateExchange_thenReturnsPageOfEmployee() {
    ResponseEntity<CustomPageImpl<EmployeeDto>> responseEntity = restTemplate.exchange(
      "http://localhost:" + port + "/organization/data",
      HttpMethod.GET,
      null,
      new ParameterizedTypeReference<CustomPageImpl<EmployeeDto>>() {}
    );

    assertEquals(200, responseEntity.getStatusCodeValue());
    PageImpl<EmployeeDto> restPage = responseEntity.getBody();
    assertNotNull(restPage);

    assertEquals(10, restPage.getTotalElements());

    List<EmployeeDto> content = restPage.getContent();
    assertNotNull(content);
}

Here, the test verifies that the call to the endpoint via restTemplate.exchange returns a successful response. It contains the body of type PageImpl<EmployeeDto> with the contents of type List<EmployeeDto> and the paging information.

7. Conclusion

In this tutorial, we looked at using RestTemplate for making HTTP requests and handling responses. We focused specifically on the issues involved in deserializing responses into Page<Entity>. Finally, we showed the use of a CustomPageImpl class alongside ParameterizedTypeReference to read the JSON into Page<EmployeeDto> successfully.

As usual, the sample code is available on GitHub.

Course – LS (cat=REST)

Get started with Spring and Spring Boot, through the Learn Spring course :

>> CHECK OUT THE COURSE
res – REST (eBook) (cat=REST)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.