Ruslan Stelmachenko opened SPR-16094 and commented

There is no way to compose several ParameterizedTypeReference s.

See for example Guava's TypeToken and it's reference documentation.

With TypeToken we can do things like this:

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);
}
...
TypeToken<Map<String, BigInteger>> mapToken = mapToken(
   TypeToken.of(String.class),
   TypeToken.of(BigInteger.class));
TypeToken<Map<Integer, Queue<String>>> complexToken = mapToken(
   TypeToken.of(Integer.class),
   new TypeToken<Queue<String>>() {});

Why this matters?

For example, if you want to make a generic API calling method, when each response is wrapped in some way:

@Data
public class ApiResponse<T> {

    private String status;
    private String message;
    private T result;

}

The T can be any type here.

The method can be written like this:

public <T> ApiResponse<T> makeRequest(URI uri, ParameterizedTypeReference<ApiResponse<T>> responseTypeToken);

To reduce boilerplate we don't want to pass

new ParameterizedTypeReference<ApiResponse<MyClass1>>() {}
// or
new ParameterizedTypeReference<ApiResponse<List<MyClass2>>>() {}

each time when we need to call our generic API method.

Instead we want to pass

new ParameterizedTypeReference<MyClass1>() {}
// or
new ParameterizedTypeReference<List<MyClass2>>() {}

So our method shold be written like this:

public <T> ApiResponse<T> makeRequest(URI uri, ParameterizedTypeReference<T> resultTypeToken);

But then, we have no way to construct ParameterizedTypeReference instance which includes ApiResponse type as wrapper of passed type.

In other words, we can't transform

new ParameterizedTypeReference<List<MyClass2>>() {}
// into
new ParameterizedTypeReference<ApiResponse<List<MyClass2>>>() {}

With TypeToken it is easy:

makeRequest(uri, new TypeToken<List<MyClass2>>() {});
// then inside makeRequest method
TypeToken<ApiResponse<T>> responseTypeToken = new TypeToken<ApiResponse<T>>() {}
        .where(new TypeParameter<T>() {}, resultTypeToken);
// responseTypeToken.getType() will return ApiResponse<List<MyClass2>>

If RestTemplate and WebClient was able to take TypeToken argument, then we were able to use it as is. But they can't (and shouldn't). So can we just add this composition feature into ParameterizedTypeReference?

-Also it will be good to have some TypeToken to ParameterizedTypeReference adapter, like this:-

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 + ">";
        }

    }

}

-But maybe this adapter is beyond the scope of spring core and more appropriate for something like spring-boot.- Forget about adapter, it is already implemented in 5.0.1 through ParameterizedTypeReference.fromType. Let's focus on ability to composition instead.


Affects: 5.0 GA