Search code examples
javaspringgenericsresttemplatetype-erasure

Passing a generic Type as responseType in RestTemplate.exchange() in Spring Rest Template


I have a candy factory that exposed various endpoints to get candies in a bag.

candy such as

  1. name: m&m ; endpoint: candy-factory/mnms
  2. name: skittle; endpoint: candy-factory/skittles
  3. name: smarties; endpoint: candy-factory/smarties

Api response pattern is below

 public class CandyWrapper <T> {
  @JsonProperty("candies")
  private List<T> candies;
}

I have a generic service class which deals with getting the candies using Spring RestTemplate

public class CandyStore<T> {

  private final RestTemplate restTemplate;

  private final Class<CandyWrapper<T>> type

  String candyFactroyUrl;

  
  public CandyStore(Class<CandyWrapper<T>> type, RestTemplate restTemplate, String 
   factoryUrl) {
    this.type = type;
    ....
    ....
   }

  public CandyWrapper<T> getCandies(){
      
       HttpEntity<HttpHeaders> request = new HttpEntity<>();
       ResponseEntity<CandyWrapper<T>> response;
       response = restTemplate.exchange(candyFactroyUrl, HttpMethod.GET, request, type);
       
       return response.getBody();
   }

}

Now when I am going to configure the CandyStore service with the actual type of the Candy ; Unable to determine the candy type to pass in the service constructor.

@Bean()
public getSkittleCandyStore(RestTemplate restTemplate, String skittleUrl){

  //TODO
   get the class of type CandyWrapper<Skittle> so that I can pass it to the service 
   constructor. 
   
   CandyWrapper<Skittle> instance = new CandyWrapper<Skittle>();

   // instance.getClass() does not give the class conforming to the parameter below.
  return new CandyStore(???, restTemplate, skittleUrl);

}

Any help to clear up the generics concepts is appreciated. Thanks!!


Solution

  • Java generics undergo type erasure, meaning they don't retain their generic type at runtime, so CandyWrapper<Skittle>.class just wont' work.

    You may try to use ParameterizedTypeReference from Spring instead, which provides an abstract class that you simply subclass to capture the generic type, like:

    @Bean
    public CandyStore<Skittle> getSkittleCandyStore(
        RestTemplate restTemplate,
        String skittleUrl
    ) {
        return new CandyStore<>(
            new ParameterizedTypeReference<CandyWrapper<Skittle>>() {},
            restTemplate,
            skittleUrl
        );
    }
    

    and CandyStore:

    public class CandyStore<T> {
    
        private final RestTemplate restTemplate;
        private final ParameterizedTypeReference<CandyWrapper<T>> typeRef;
        String candyFactroyUrl;
    
        public CandyStore(ParameterizedTypeReference<CandyWrapper<T>> typeRef, RestTemplate restTemplate, String factoryUrl) {
            this.typeRef = typeRef;
            this.restTemplate = restTemplate;
            this.candyFactroyUrl = factoryUrl;
        }
    
        public CandyWrapper<T> getCandies(){
            HttpEntity<HttpHeaders> request = new HttpEntity<>();
            ResponseEntity<CandyWrapper<T>> response;
            response = restTemplate.exchange(candyFactroyUrl, HttpMethod.GET, request, typeRef);
            return response.getBody();
        }
    }
    

    so please, make sure to update the restTemplate.exchange to use the new typeRef field instead, should looks like:

    response = restTemplate.exchange(candyFactroyUrl, HttpMethod.GET, request, typeRef);
    

    Read more about ParameterizedTypeReference: