Using Spring RestTemplate in generic method with generic parameter
Solution 1
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.
Solution 2
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 )
Solution 3
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();
}
}
Solution 4
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
Solution 5
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>>() {});
Artyom Kozhemiakin
Updated on July 05, 2022Comments
-
Artyom Kozhemiakin almost 2 years
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 gettingResponseEntity<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
withClass
key (Proposed by @Sotirios in the end of his answer). Would someone mind to give an example? -
Cristina_eGold almost 9 yearsThe 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...
-
IonSpin almost 9 yearsI copied the ParametrizedTypeImpl implementation from Google Gson project, the link is in the answer, and also here again code.google.com/p/google-gson/source/browse/trunk/gson/src/main/… :)
-
Cristina_eGold almost 9 yearsAh, sorry, you are completely right. Thanks a bunch! :)
-
loyalBrown over 8 yearsAlso, 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.
-
ankit almost 8 yearsI 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...
-
Dilettante44 almost 8 yearsthe 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.
-
Ronald over 7 yearsAn explanation is needed for your code. Please review SO's policies on how-to-answer.
-
Gonçalo over 7 yearsIn 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.
-
VelNaga almost 7 yearsBut he is hardcoded the class({MyClass.class}) i want to pass the class as dynamic also it can be List<MyClass.class> in my case...Your suggestion is appreciated.
-
VelNaga almost 7 yearsAlso what is the drawback for this answer(stackoverflow.com/a/41629503/3656056) It works fine for CustomObject<T> but fails for CustomObject<List<T>>
-
VelNaga almost 7 yearsHi,Thanks for your answer it works for CustomObject<T> but it fails for CustomObject<List<T>> could you please help me to resolve the issue for CustomObject<List<T>>
-
Sotirios Delimanolis almost 7 years@VelNaga There is no
Class
object forList<MyClass>
, such a thing does not exist. There can be aParameterizedType
forList<MyClass>
but you either have to construct it yourself or use the type token trick. -
Sotirios Delimanolis almost 7 years@VelNaga That solution fails for the same reason your attempt did,
List<MyClass>
doesn't exist. Since their method accepts aClass
argument, they can only "nest" one type argument. -
Sotirios Delimanolis almost 7 years@VelNaga
TypeToken
in Gson,TypeReference
in Jackson,ParameterizedTypeReference
inRestTemplate
, it's all the same pattern, called type token. -
VelNaga almost 7 yearsLet us continue this discussion in chat.
-
Sotirios Delimanolis almost 7 yearsThis answer is poorly explained and the solution is limited. The fact that you use a
Class<T>
parameter limits the solution to non-generic types. -
Ken Pronovici almost 6 yearsThis worked well for my use case, which was a generic mechanism to parse paginated data using a GitHub-style Link header. It gave me a way to create a valid type to pass into a Spring HttpMessageConverterExtractor, letting me keep the interface to my class generic.
-
Blangero about 5 yearsI 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!
-
Ayman Arif almost 4 yearsI 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 almost 4 years@Blangero I am getting java.lang.IllegalArgumentException: Mismatched number of generics specified for your comment.
-
Tony Falabella over 3 yearsWhat a great solution! Worked like a charm.
-
chrylis -cautiouslyoptimistic- over 3 yearsAs of Spring 4.3.12, you can use
ParameterizedTypeReference.forType(typeToken.getType())
.