akozhemiakin akozhemiakin - 6 months ago 1143
Java Question

Using Spring RestTemplate in generic method with generic parameter

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

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 see 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.

Comments