javaspringspring-bootspring-restcontrollerspring-rest

How to consume a REST API using restTemplate that returns an object with Page object within it?


API request and response:

Request: http://localhost:5555/api/products?page=1&size=100

Response:

{
    "reports": {
        "content": [
            {
                "productCatTx": "ERROR_product",
                "productValTx": "000",
                "productShrtDescTx": "NO_ERROR",
                "productLongDescTx": "No Error in response"
            },
            {
                "productCatTx": "ERROR_product",
                "productValTx": "010",
                "productShrtDescTx": "PRODUCT_ACCEPTED",
                "productLongDescTx": "Product Accepted"
            }
        ],
        "pageable": {
            "sort": {
                "empty": false,
                "sorted": true,
                "unsorted": false
            },
            "offset": 0,
            "pageNumber": 0,
            "pageSize": 100,
            "unpaged": false,
            "paged": true
        },
        "totalElements": 321,
        "totalPages": 4,
        "last": false,
        "size": 100,
        "number": 0,
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "numberOfElements": 100,
        "first": true,
        "empty": false
    }
}

Client REST API call from another project:

@GetMapping("/paginatedWResponseData")
    public void getProductsWResponseData() {
        RestTemplate restTemplate = new RestTemplate();
        String resourceUrl = "http://localhost:5555/api/products?page=1&size=100";

        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(resourceUrl)
                .queryParam("page", 1)
                .queryParam("size", 100);

        ResponseEntity<ReportResponse> responseEntity = restTemplate.exchange(uriBuilder.toUriString(),
                HttpMethod.GET, null, ReportResponse.class);

        System.out.println(responseEntity);
    }

ReportResponse.java

@Data
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ReportResponse extends CustomPageImpl implements Serializable {

    Page<Product> reportData;

    @JsonCreator
    public ReportResponse(@JsonProperty("reports") Page<Product> reportData) {
        super(reportData.getContent());
        this.reportData = reportData;
    }

}

CustomPageImpl.java

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<>());
    }
}

Error:

2023-08-24T13:58:53.372-04:00 ERROR 20304 --- [nio-5656-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Page]] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Page` (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: 12] (through reference chain: net.code.sample.ReportResponse["reports"])

Solution

  • The stacktrace is telling you that Jackson library can't create a org.springframework.data.domain.Page instance because it has no constructor, and it makes sense because is an interface.

    I think what you actually need is adjust ReportResponse to receive a CustomPageImpl instance as argument in its constructor. Additionally you could also get rid of inheritance like this:

    @Data
    @Builder
    @FieldDefaults(level = AccessLevel.PRIVATE)
    public class ReportResponse implements Serializable {
    
        private final CustomPageImpl<Product> reportData;
    
        @JsonCreator
        public ReportResponse(@JsonProperty("reports") CustomPageImpl<Product> reportData) {
            this.reportData = reportData;
        }
    
        public CustomPageImpl<Product> getReportData() {
            return this.reportData;
        }
    }