Using Spring RestTemplate in generic method with generic parameter
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.
Spring RestTemplate and generic types ParameterizedTypeReference collections like ListT
I worked around this using the following generic method:
public <T> List<T> exchangeAsList(String uri, ParameterizedTypeReference<List<T>> responseType) {
return restTemplate.exchange(uri, HttpMethod.GET, null, responseType).getBody();
}
Then I could call:
List<MyDto> dtoList = this.exchangeAsList("http://my/url", new ParameterizedTypeReference<List<MyDto>>() {});
This did burden my callers with having to specify the ParameterizedTypeReference
when calling, but meant that I did not have to keep a static mapping of types like in vels4j's answer
Generic restTemplateExchange with generic responseType
I found this answer, and it was working for me:
Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericType>"
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange(pUrl, HttpMethod.POST, pEntity, new ParameterizedTypeReference<ResultListPage<MyObject>>() {});
RestTemplate use with a generic response type
can use String
ResponseEntity<String> result = getRestTemplate().exchange(uri, HttpMethod.POST, httpEntity,
new ParameterizedTypeReference<String>() {
}, userId);
Generics with Spring RESTTemplate
ParameterizedTypeReference has been introduced in 3.2 M2 to workaround this issue.
Wrapper<Model> response = restClient.exchange(loginUrl,
HttpMethod.GET,
null,
new ParameterizedTypeReference<Wrapper<Model>>() {}).getBody();
However, the postForObject/getForObject variant was not introduced.
Spring RestTemplate: how to define the parameter type in a generic class
So I found a solution, which is to override the getType
method of ParameterizedTypeReference
returning a custom made ParameterizedType
like this:
public abstract class GenericClient<DTO> {
private final Class<DTO> dtoClass;
... ctor which sets dtoClass ...
public List<DTO> findAll() {
...
URI uri = ...
ResponseEntity<List<DTO>> exchange = getRestTemplate()
.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<List<DTO>() {
@Override
public Type getType() {
return new ParameterizedType() {
@Override
public Type getRawType() {
return List.class;
}
@Override
public Type getOwnerType() {
return null;
}
@Override
public Type[] getActualTypeArguments() {
return new Type[]{dtoClass};
}
};
});
return exchange.getBody(); // OK
}
}
Why doesn't this code work when wrapped in a generic function?
Answer for 1.
ParameterizedTypeReference<X> typeRef = new ParameterizedTypeReference<X>() {};
Thanks the final {}
jackson is able to find out the what X
is in run-time using reflection however X
is resolved in compilation time so if you have MyClass
or T
that is exactly what it will get in runtime; It won't be able to figure out what the T
is assigned to in runtime.
For the very same reason, if you keep using the function-less option but you remove the {}
at the end it will compile but it will result in the same error.
Answer for 2.
Instead of Class<T> returnType
, that you never make reference to btw, you could pass ParameterizedTypeReference<T> typeRef
directly. The code that call the post would then need to determine T
in compilation time:
@Test
public void with_function() {
ParameterizedTypeReference<MyClass> typeRef = new ParameterizedTypeReference<>() {};
MyClass returnValue = post("some text", typeRef, "/test");
}
}
However I think you should consider alternatives that do not rely on the {}
trick which might be problematic.
Have you tried ParameterizedTypeReference
's forType
?:
public <T> T post(Object content, Class<T> returnType, String url){
ParameterizedTypeReference<T> typeRef = ParameterizedTypeReference.forType(returnType);
HttpEntity<Object> requestEntity = new HttpEntity<>(content);
ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, typeRef);
return response.getBody();
}
In any case this will work with non-generic assignations to T
like MyClass
as when passing MyClass.class
as the return type; It would not work with ArrayList<MyClass> list; list.getClass()
since it would be equivalent to return ArrayList.class
. I guess in those cases you would need to construct and pass a different Type
instance that would correspond to the more complex type expression.
Related Topics
How to Apply Multiple Predicates to a Java.Util.Stream
Why Do I Need a Functional Interface to Work with Lambdas
Pros and Cons of Package Private Classes in Java
How to Programmatically Inject a Java Cdi Managed Bean into a Local Variable in a (Static) Method
Java Byte Array Contains Negative Numbers
How to Add Close Button to a Jtabbedpane Tab
When Using == for a Primitive and a Boxed Value, Is Autoboxing Done, or Is Unboxing Done
How to Change Java Logging Console Output from Std Err to Std Out
Run Java File as Administrator with Full Privileges
Printing a Large Swing Component
Conversion from 12 Hours Time to 24 Hours Time in Java
Are Thread.Sleep(0) and Thread.Yield() Statements Equivalent
How to Round Time to the Nearest Quarter Hour in Java
Jackson Dynamic Property Names
Understanding the etc/Gmt Time Zone
Passing an Array or List to @Pathvariable - Spring/Java
Why Can't You Reduce the Visibility of a Method in a Java Subclass