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?
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>") So if I'm trying to do something like this, all is OK.
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 ...
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.
Each method with a return type expects a generic class type as a parameter to determine the type of response. 4. RestTemplate Demonstrations class. All of the following examples are in the class. It’s a simple client that wraps and provides Employee-related methods. As always, you can find the code in our GitHub Repository. is quite unspectacular.
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.
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 )
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With