1

Similar to this question, I'm trying to exchange information between two microservices using Spring RestTemplate. The difference in my case is that I'm working with an abstract class as a generic type parameter, and I'm encountering difficulties.

Here's an example of my class:

public <T extends Account> Page<T> getAllAccounts() {   // not working
    ParameterizedTypeReference<Page<T>> responseType = new ParameterizedTypeReference<Page<T>>() { };
    ResponseEntity<Page<T>> result = restTemplate.exchange("http://localhost:8080/api/v1/accounts/", HttpMethod.GET, null, responseType);

    Page<T> accounts = result.getBody();
    
    return accounts;
}

The function behind the API call has the return type public <T extends Account> Page<T> getAccounts(...) {...}. The type T can be any class that extends Account, as there are multiple Account-Types available.This method works fine for non-abstract classes (e.g., Page<CustomerAccount>), but it fails with abstract classes.

So, how can I communicate between a microservice running on port 8081 and another microservice running on port 8080, using "abstract generics" (or a similar concept) with Spring RestTemplate?

When I try to call the function, I receive the following error message:

This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Dec 18 14:55:33 CET 2020
There was an unexpected error (type=Internal Server Error, status=500).
Type definition error: [simple type, class org.springframework.data.domain.Page]; nested exception is 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: (PushbackInputStream); line: 1, column: 1]
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Page]; nested exception is 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: (PushbackInputStream); line: 1, column: 1]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:282)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:243)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:105)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1034)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1017)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:777)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:710)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:630)
    at de.rwth.swc.lab.ws2021.daifu.businesslogic.services.AccountService.getAllAccounts(AccountService.java:50)
    at de.rwth.swc.lab.ws2021.daifu.businesslogic.api.AccountController.getAllAccounts(AccountController.java:47)
... (rest shortend for better readability)
Gykonik
  • 638
  • 1
  • 7
  • 24
  • 1
    You're trying to instantiate new instances through this method, it's better to use generics in this case for already instantiated objects. That said, you should probably ditch the generics here, and utilize the methods available to you through `Account`/etc. You could also look into factory, memento, and decorator design patterns. Lastly, note this line in the error in particular: `abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information` – Rogue Dec 18 '20 at 14:14
  • What do you mean by "ditch teh generics"? The return of the request should contain all possible classes, which extend `account`. Therefore, I can't just leave you out. Besides, the other API service is predefined and must not be changed. I would prefer to be able to fix the error - could cou tell me how? :) – Gykonik Dec 18 '20 at 16:10
  • You could just return a `Page` (which is what you want), and then you would provide a deserializer to turn the stored data into an `Account` object. Hence, the factory pattern. – Rogue Dec 18 '20 at 16:23
  • But then I would lose information from classes which extend Account e.g. by another String customerName or something, wouldn't I? – Gykonik Dec 18 '20 at 16:25
  • Just because you refer to something as its abstract type, doesn't change the underlying object. For an adjacent example, in `List ex = new ArrayList<>();`, `ex` would still be an `ArrayList` (and could be casted to such or checked via `instanceof`), but is referred to as a `List`. Furthermore, `List` provides the methods needed to use it appropriately. – Rogue Dec 18 '20 at 16:27
  • Ahh okay, that makes sense. Thanks! However, if I change the return type to `Page` and adjust the types in the getAllAccounts-function, I still get the same error :/ – Gykonik Dec 18 '20 at 16:32
  • Correct, and for the same reason: you need to specify something (a deserializer) that turns the saved back-end data into objects. In your case, `Account` objects: you'd create the object which was relevant and return it accordingly. The program still doesn't know how to turn your data into those objects. – Rogue Dec 18 '20 at 16:36
  • Mhh, do you have a link with an example for creating such a deserializer? I've never heard of it before... I'm not sure how to implement a deserializer and how to use it in my code :/ – Gykonik Dec 18 '20 at 16:39
  • It really depends a lot on what you're doing in your actual code, enough that I'll say it'll be easier for you to google around things like "spring boot deserializer" and parsing through some examples. From there look up the javadocs of the classes used in the examples – Rogue Dec 18 '20 at 16:41
  • Sadly also after 2 hours of googling I have absolutly no Idea how I have to do this... I tried several things and also switched to webCliend (because I found out restTemplate is deprecated) and still the same error and I have no clue how to use a serializer / build a custom one... – Gykonik Dec 18 '20 at 19:03

0 Answers0