Using Spring RestTemplate in generic method with generic parameter

Artyom Kozhemiakin picture Artyom Kozhemiakin · Feb 24, 2014 · Viewed 100.6k times · Source

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?

Answer

Sotirios Delimanolis picture Sotirios Delimanolis · Feb 24, 2014

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.