84

To use generic types with Spring RestTemplate we need to use ParameterizedTypeReference (Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>")

Suppose I have some class

public class MyClass {
    int users[];

    public int[] getUsers() { return users; }
    public void setUsers(int[] users) {this.users = users;}
}

And some wrapper class

public class ResponseWrapper <T> {
    T response;

    public T getResponse () { return response; }
    public void setResponse(T response) {this.response = response;}
}

So if I'm trying to do something like this, all is OK.

public ResponseWrapper<MyClass> makeRequest(URI uri) {
    ResponseEntity<ResponseWrapper<MyClass>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {});
    return response;
}

But when I'm trying to create generic variant of the above method ...

public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
   ResponseEntity<ResponseWrapper<T>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        new ParameterizedTypeReference<ResponseWrapper<T>>() {});
    return response;
}

... and calling this method like so ...

makeRequest(uri, MyClass.class)

... instead of getting ResponseEntity<ResponseWrapper<MyClass>> object I'm getting ResponseEntity<ResponseWrapper<LinkedHashSet>> object.

How can I solve this problem? Is it a RestTemplate bug?

UPDATE 1 Thanks to @Sotirios I understand the concept. Unfortunately I'm newly registered here so I cant comment on his answer, so writing it here. Im not sure that I clearly understand how to implement the proposed approach to solve my problem with Map with Class key (Proposed by @Sotirios in the end of his answer). Would someone mind to give an example?

Community
  • 1
  • 1
Artyom Kozhemiakin
  • 1,432
  • 1
  • 13
  • 13
  • 1
    You should always be able to comment on answers to your questions. The proposed solution works like this. You create a `Map>>`. You add pre-constructed `ParameterizedTypeReference` instances for actual types you expect to the `Map` for the corresponding `Class`. – Sotirios Delimanolis Feb 25 '14 at 17:27
  • @SotiriosDelimanolis - Can you rephrase for someone that's new to Java? I'm coming from a C# background, so this has been quite the interesting experience for me. – wesm Jul 29 '15 at 23:08

12 Answers12

69

No, it is not a bug. It is a result of how the ParameterizedTypeReference hack works.

If you look at its implementation, it uses Class#getGenericSuperclass() which states

Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by this Class.

If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code.

So, if you use

new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {}

it will accurately return a Type for ResponseWrapper<MyClass>.

If you use

new ParameterizedTypeReference<ResponseWrapper<T>>() {}

it will accurately return a Type for ResponseWrapper<T> because that is how it appears in the source code.

When Spring sees T, which is actually a TypeVariable object, it doesn't know the type to use, so it uses its default.

You cannot use ParameterizedTypeReference the way you are proposing, making it generic in the sense of accepting any type. Consider writing a Map with key Class mapped to a predefined ParameterizedTypeReference for that class.

You can subclass ParameterizedTypeReference and override its getType method to return an appropriately created ParameterizedType, as suggested by IonSpin.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • 1
    But he is hardcoded the class({MyClass.class}) i want to pass the class as dynamic also it can be List in my case...Your suggestion is appreciated. – VelNaga Jun 30 '17 at 14:55
  • Also what is the drawback for this answer(https://stackoverflow.com/a/41629503/3656056) It works fine for CustomObject but fails for CustomObject> – VelNaga Jun 30 '17 at 14:59
  • @VelNaga There is no `Class` object for `List`, such a thing does not exist. There can be a `ParameterizedType` for `List` but you either have to construct it yourself or use the type token trick. – Sotirios Delimanolis Jun 30 '17 at 15:08
  • @VelNaga That solution fails for the same reason your attempt did, `List` doesn't exist. Since their method accepts a `Class` argument, they can only "nest" one type argument. – Sotirios Delimanolis Jun 30 '17 at 15:09
  • @VelNaga `TypeToken` in Gson, `TypeReference` in Jackson, `ParameterizedTypeReference` in `RestTemplate`, it's all the same pattern, called _type token_. – Sotirios Delimanolis Jun 30 '17 at 15:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/148070/discussion-between-velnaga-and-sotirios-delimanolis). – VelNaga Jun 30 '17 at 15:10
36

I am using org.springframework.core.ResolvableType for a ListResultEntity :

    ResolvableType resolvableType = ResolvableType.forClassWithGenerics(ListResultEntity.class, itemClass);
    ParameterizedTypeReference<ListResultEntity<T>> typeRef = ParameterizedTypeReference.forType(resolvableType.getType());

So in your case:

public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
   ResponseEntity<ResponseWrapper<T>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        ParameterizedTypeReference.forType(ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz)));
    return response;
}

This only makes use of spring and of course requires some knowledge about the returned types (but should even work for things like Wrapper>> as long as you provide the classes as varargs )

Justin
  • 496
  • 4
  • 4
  • 9
    I think you mean ResolvableType.forClassWithGenerics(ResponseWrapper.class, clazz).getType() , right? Your post is working in my project, and I think it's the shortest and most simple one. Thanks Justin! – Blangero Mar 05 '19 at 05:52
  • @Blangero I am getting java.lang.IllegalArgumentException: Mismatched number of generics specified for your comment. – Ayman Arif Jun 18 '20 at 03:46
  • This works, simply. Bravo! – Jin Kwon Feb 10 '23 at 08:17
22

As the code below shows it, it works.

public <T> ResponseWrapper<T> makeRequest(URI uri, final Class<T> clazz) {
   ResponseEntity<ResponseWrapper<T>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        new ParameterizedTypeReference<ResponseWrapper<T>>() {
            public Type getType() {
                return new MyParameterizedTypeImpl((ParameterizedType) super.getType(), new Type[] {clazz});
        }
    });
    return response;
}

public class MyParameterizedTypeImpl implements ParameterizedType {
    private ParameterizedType delegate;
    private Type[] actualTypeArguments;

    MyParameterizedTypeImpl(ParameterizedType delegate, Type[] actualTypeArguments) {
        this.delegate = delegate;
        this.actualTypeArguments = actualTypeArguments;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return actualTypeArguments;
    }

    @Override
    public Type getRawType() {
        return delegate.getRawType();
    }

    @Override
    public Type getOwnerType() {
        return delegate.getOwnerType();
    }

}
김원겸
  • 229
  • 2
  • 3
  • 2
    An explanation is needed for your code. Please review SO's policies on how-to-answer. – Rav Jan 13 '17 at 07:55
  • 1
    In fact this is the best/simplest answer i've seen so far. You just need to use the MyParameterizedTypeImpl as showed and you don't need the static Map with the actual types and wrappers of type. Of course the class must be passed in method or constructor. – Gonçalo Jan 27 '17 at 10:13
  • Hi,Thanks for your answer it works for CustomObject but it fails for CustomObject> could you please help me to resolve the issue for CustomObject> – VelNaga Jun 30 '17 at 15:00
  • 3
    This answer is poorly explained and the solution is limited. The fact that you use a `Class` parameter limits the solution to non-generic types. – Sotirios Delimanolis Jun 30 '17 at 15:11
  • I am getting the following error for RestTemplate `java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.reflect.ParameterizedType` – Ayman Arif Jun 18 '20 at 03:39
11

As Sotirios explains, you can not use the ParameterizedTypeReference, but ParameterizedTypeReference is used only to provide Type to the object mapper, and as you have the class that is removed when type erasure happens, you can create your own ParameterizedType and pass that to RestTemplate, so that the object mapper can reconstruct the object you need.

First you need to have the ParameterizedType interface implemented, you can find an implementation in Google Gson project here. Once you add the implementation to your project, you can extend the abstract ParameterizedTypeReference like this:

class FakeParameterizedTypeReference<T> extends ParameterizedTypeReference<T> {

@Override
public Type getType() {
    Type [] responseWrapperActualTypes = {MyClass.class};
    ParameterizedType responseWrapperType = new ParameterizedTypeImpl(
        ResponseWrapper.class,
        responseWrapperActualTypes,
        null
        );
    return responseWrapperType;
    }
}

And then you can pass that to your exchange function:

template.exchange(
    uri,
    HttpMethod.POST,
    null,
    new FakeParameterizedTypeReference<ResponseWrapper<T>>());

With all the type information present object mapper will properly construct your ResponseWrapper<MyClass> object

IonSpin
  • 1,255
  • 1
  • 14
  • 16
  • The ParameterizedTypeImpl class in the JDK has private access on its constructor and no setters. Have you used a library for this? I can't see any way of instantiating that class without reflection... – Cristina_eGold Jun 09 '15 at 14:35
  • I copied the ParametrizedTypeImpl implementation from Google Gson project, the link is in the answer, and also here again http://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/ParameterizedTypeImpl.java?r=597 :) – IonSpin Jun 09 '15 at 14:57
  • 1
    Ah, sorry, you are completely right. Thanks a bunch! :) – Cristina_eGold Jun 09 '15 at 15:08
  • Also, the ParameterizedTypeImpl class in the JDK has a private constructor because its meant to be used as a singleton. Its API has a make method you can use to create a ParameterizedType. – loyalBrown Sep 29 '15 at 11:04
11

Actually, you can do this, but with additional code.

There is Guava equivalent of ParameterizedTypeReference and it's called TypeToken.

Guava's class is much more powerful then Spring's equivalent. You can compose the TypeTokens as you wish. For example:

static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
  return new TypeToken<Map<K, V>>() {}
    .where(new TypeParameter<K>() {}, keyToken)
    .where(new TypeParameter<V>() {}, valueToken);
}

If you call mapToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class)); you will create TypeToken<Map<String, BigInteger>>!

The only disadvantage here is that many Spring APIs require ParameterizedTypeReference and not TypeToken. But we can create ParameterizedTypeReference implementation which is adapter to TypeToken itself.

import com.google.common.reflect.TypeToken;
import org.springframework.core.ParameterizedTypeReference;

import java.lang.reflect.Type;

public class ParameterizedTypeReferenceBuilder {

    public static <T> ParameterizedTypeReference<T> fromTypeToken(TypeToken<T> typeToken) {
        return new TypeTokenParameterizedTypeReference<>(typeToken);
    }

    private static class TypeTokenParameterizedTypeReference<T> extends ParameterizedTypeReference<T> {

        private final Type type;

        private TypeTokenParameterizedTypeReference(TypeToken<T> typeToken) {
            this.type = typeToken.getType();
        }

        @Override
        public Type getType() {
            return type;
        }

        @Override
        public boolean equals(Object obj) {
            return (this == obj || (obj instanceof ParameterizedTypeReference &&
                    this.type.equals(((ParameterizedTypeReference<?>) obj).getType())));
        }

        @Override
        public int hashCode() {
            return this.type.hashCode();
        }

        @Override
        public String toString() {
            return "ParameterizedTypeReference<" + this.type + ">";
        }

    }

}

Then you can use it like this:

public <T> ResponseWrapper<T> makeRequest(URI uri, Class<T> clazz) {
   ParameterizedTypeReference<ResponseWrapper<T>> responseTypeRef =
           ParameterizedTypeReferenceBuilder.fromTypeToken(
               new TypeToken<ResponseWrapper<T>>() {}
                   .where(new TypeParameter<T>() {}, clazz));
   ResponseEntity<ResponseWrapper<T>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        responseTypeRef);
    return response;
}

And call it like:

ResponseWrapper<MyClass> result = makeRequest(uri, MyClass.class);

And the response body will be correctly deserialized as ResponseWrapper<MyClass>!

You can even use more complex types if you rewrite your generic request method (or overload it) like this:

public <T> ResponseWrapper<T> makeRequest(URI uri, TypeToken<T> resultTypeToken) {
   ParameterizedTypeReference<ResponseWrapper<T>> responseTypeRef =
           ParameterizedTypeReferenceBuilder.fromTypeToken(
               new TypeToken<ResponseWrapper<T>>() {}
                   .where(new TypeParameter<T>() {}, resultTypeToken));
   ResponseEntity<ResponseWrapper<T>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        responseTypeRef);
    return response;
}

This way T can be complex type, like List<MyClass>.

And call it like:

ResponseWrapper<List<MyClass>> result = makeRequest(uri, new TypeToken<List<MyClass>>() {});
Ruslan Stelmachenko
  • 4,987
  • 2
  • 36
  • 51
2

I have another way to do this... suppose you swap out your message converter to String for your RestTemplate, then you can receive raw JSON. Using the raw JSON, you can then map it into your Generic Collection using a Jackson Object Mapper. Here's how:

Swap out the message converter:

    List<HttpMessageConverter<?>> oldConverters = new ArrayList<HttpMessageConverter<?>>();
    oldConverters.addAll(template.getMessageConverters());

    List<HttpMessageConverter<?>> stringConverter = new ArrayList<HttpMessageConverter<?>>();
    stringConverter.add(new StringHttpMessageConverter());

    template.setMessageConverters(stringConverter);

Then get your JSON response like this:

    ResponseEntity<String> response = template.exchange(uri, HttpMethod.GET, null, String.class);

Process the response like this:

     String body = null;
     List<T> result = new ArrayList<T>();
     ObjectMapper mapper = new ObjectMapper();

     if (response.hasBody()) {
        body = items.getBody();
        try {
            result = mapper.readValue(body, mapper.getTypeFactory().constructCollectionType(List.class, clazz));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            template.setMessageConverters(oldConverters);
        }
        ...
  • I like this solution the best. But what is "clazz"? I guess it in terms of T somehow, but I'm not able to express it... – ankit Aug 20 '16 at 05:21
  • the ObjectMapper's factory's constructCollectionType method takes two inputs, the first one is a Collection class, the second one (which I labeled "clazz") is the class of the objects contained in the aforementioned Collection class. – Dilettante44 Aug 23 '16 at 20:04
1

I find this to be a more elegant solution:

private static <T> ParameterizedTypeReference<BaseResponse<T>> typeReferenceOf ( Class<T> tClass ) {
    return ParameterizedTypeReference.forType( sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make( BaseResponse.class, new Type[]{ tClass }, null ) );
}

For example, given the following BaseResponse and ResponseData classes:

@Getter
@Setter
public static class BaseResponse<T> {
    
    private ResponseData<T> response;
    
    public BaseResponse () { }
    
    public boolean hasData () {
        return response != null;
    }

    public T data () {
        return response.data;
    }
    
}

@Getter
@Setter
public static final class ResponseData<T> {
    
    private T data;
    
    public ResponseData () { }
    
}

And given a sample get method, using WebClient:

public <T> Mono <T> get ( URI uri, Class<T> tClass ) {
    
    return webClient
        .get            ()
        .uri            ( uriBuilder        -> uriBuilder.pathSegment( uri.getPath() ).build() )
        .exchangeToMono ( clientResponse    -> clientResponse.bodyToMono( typeReferenceOf( tClass ) ) )
        .flatMap        ( baseResponse      -> baseResponse.hasData() ? Mono.just( baseResponse.data() ) : Mono.empty()  );
    
}
Raccoon
  • 63
  • 6
0

Note: This answer refers/adds to Sotirios Delimanolis's answer and comment.

I tried to get it to work with Map<Class, ParameterizedTypeReference<ResponseWrapper<?>>>, as indicated in Sotirios's comment, but couldn't without an example.

In the end, I dropped the wildcard and parametrisation from ParameterizedTypeReference and used raw types instead, like so

Map<Class<?>, ParameterizedTypeReference> typeReferences = new HashMap<>();
typeReferences.put(MyClass1.class, new ParameterizedTypeReference<ResponseWrapper<MyClass1>>() { });
typeReferences.put(MyClass2.class, new ParameterizedTypeReference<ResponseWrapper<MyClass2>>() { });

...

ParameterizedTypeReference typeRef = typeReferences.get(clazz);

ResponseEntity<ResponseWrapper<T>> response = restTemplate.exchange(
        uri, 
        HttpMethod.GET, 
        null, 
        typeRef);

and this finally worked.

If anyone has an example with parametrisation, I'd be very grateful to see it.

Ginkobonsai
  • 177
  • 3
  • 13
0

My own implementation of generic restTemplate call:

private <REQ, RES> RES queryRemoteService(String url, HttpMethod method, REQ req, Class reqClass) {
    RES result = null;
    try {
        long startMillis = System.currentTimeMillis();

        // Set the Content-Type header
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(new MediaType("application","json"));            

        // Set the request entity
        HttpEntity<REQ> requestEntity = new HttpEntity<>(req, requestHeaders);

        // Create a new RestTemplate instance
        RestTemplate restTemplate = new RestTemplate();

        // Add the Jackson and String message converters
        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

        // Make the HTTP POST request, marshaling the request to JSON, and the response to a String
        ResponseEntity<RES> responseEntity = restTemplate.exchange(url, method, requestEntity, reqClass);
        result = responseEntity.getBody();
        long stopMillis = System.currentTimeMillis() - startMillis;

        Log.d(TAG, method + ":" + url + " took " + stopMillis + " ms");
    } catch (Exception e) {
         Log.e(TAG, e.getMessage());
    }
    return result;
}

To add some context, I'm consuming RESTful service with this, hence all requests and responses are wrapped into small POJO like this:

public class ValidateRequest {
  User user;
  User checkedUser;
  Vehicle vehicle;
}

and

public class UserResponse {
  User user;
  RequestResult requestResult;
}

Method which calls this is the following:

public User checkUser(User user, String checkedUserName) {
    String url = urlBuilder()
            .add(USER)
            .add(USER_CHECK)
            .build();

    ValidateRequest request = new ValidateRequest();
    request.setUser(user);
    request.setCheckedUser(new User(checkedUserName));

    UserResponse response = queryRemoteService(url, HttpMethod.POST, request, UserResponse.class);
    return response.getUser();
}

And yes, there's a List dto-s as well.

AlexV
  • 743
  • 6
  • 9
0

I feel like there's a much easier way to do this... Just define a class with the type parameters that you want. e.g.:


final class MyClassWrappedByResponse extends ResponseWrapper<MyClass> {
    private static final long serialVersionUID = 1L;
}

Now change your code above to this and it should work:

public ResponseWrapper<MyClass> makeRequest(URI uri) {
    ResponseEntity<MyClassWrappedByResponse> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        MyClassWrappedByResponse.class
    return response;
}
byronaltice
  • 623
  • 6
  • 18
0
Abc is come object.

HttpEntity<Abc> httpEntity= new HttpEntity<>( headers );
ResponseEntity<Abc> resp = null;

resp = restCall( doUrl, HttpMethod.GET, httpEntity, new ParameterizedTypeReference<Abc>() {} );
//----------------------------------------------

public <T> ResponseEntity restCall( String doUrl, HttpMethod httpMethod, HttpEntity<?> httpEntity, ParameterizedTypeReference respRef )
    {
    try {
        return restTemplate.exchange( doUrl, httpMethod, httpEntity, respRef );
        }
    catch( HttpClientErroException exc )
        {
        do whatever
        }
    }

//--------------------------  can also use a generic inside
public class ComResp<T> {
    private T data;
    public ComResp( T data )
    { this.data = data }
}

ResponseEntity<ComResp<Abc>> resp = null;

resp = restCall( doUrl, HttpMethod.GET, httpEntity, new ParameterizedTypeReference<ComResp<Abc>>() {} );

// spring boot 2.5.3
Stan Towianski
  • 411
  • 2
  • 6
0

instead of Class<T> you can create a function that takes ParameterizedTypeReference<ResponseWrapper<T>> as paramemter :

public <T> ResponseWrapper<T> makeRequest(URI uri, ParameterizedTypeReference<ResponseWrapper<T>> response) {
   ResponseEntity<ResponseWrapper<T>> response = template.exchange(
        uri,
        HttpMethod.POST,
        null,
        responseExchange);
    return response;
}

when calling this function provide an instance of ParameterizedTypeReference with the response class in Generic:

makeRequest(url, new ParameterizedTypeReference<ResponseWrapper<MyClass>>() {})
Rahul verma
  • 176
  • 6