I have these interfaces:

interface Query<T>

interface QueryHandler<in TQuery, TResult : Any> where TQuery : Query<TResult> {
    fun execute(query: TQuery): TResult
}

and dispatcher:

abstract class QueryDispatcher {
    abstract fun <TQuery, TResult : Any> dispatch(query: TQuery, queryType: KClass<TQuery>, resultType: KClass<TResult>): TResult where TQuery : Query<TResult>
    inline fun <reified TQuery, reified TResult : Any> dispatch(query: TQuery) where TQuery : Query<TResult> =
        dispatch(query, TQuery::class, TResult::class)
}

class InMemoryQueryDispatcher(
    private val applicationContext: GenericWebApplicationContext
) : QueryDispatcher() {
    override fun <TQuery : Query<TResult>, TResult : Any> dispatch(query: TQuery, queryType: KClass<TQuery>, resultType: KClass<TResult>): TResult {
        val beanFactory = applicationContext.beanFactory

        val resolvableType = ResolvableType.forClassWithGenerics(QueryHandler::class.java, queryType.java, resultType.java)
        val beans = applicationContext.getBeanNamesForType(resolvableType)

        val handler = beanFactory.getBean<QueryHandler<TQuery, TResult>>(beans.first())
        ...
    }
}

This handler implementation with non-generic TResult works:

class GetUserHandler() : QueryHandler<GetUser, UserDto> { ... }

but when I use another generic there:

class GetUsersHandler() : QueryHandler<GetUsers, List<UserDto>> { ... }

Spring cannot find my handler bean.

My findings: ResolvableType.forClassWithGenerics() produces ResolvableType QueryHandler<GetUsers, List<?>>, so when Spring iterates through all beans and checks the one I am looking for, ResolvableType::isAssignableFrom matches QueryHandler, then GetUsers, then List and fails on matching ? with UserDto - this condition returns false

My other idea was to get all nested generics inside the inlined function (so I would have [GetUser, List, UserDto]) and pass them all to the forClassWithGenerics(), but this fails for me because clazz has only two "top level" types and so won't accept three.

I don't know if I am doing something wrong or if there is some problem (either bug or language limitation?), so is this somehow possible? Thank you!

Comment From: IIShabalin

Hi @leoshusar,

What you're observing must be an effect of type erasure in Java: - existed in Java 8: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html - present in Java 17: https://docs.oracle.com/en/java/javase/17/docs/specs/patterns-switch-jls.html - article on Baeldung: https://www.baeldung.com/java-type-erasure

According to the docs, it's just not possible to distinguish the second type parameter of QueryHandler, because it's unbounded (defined as type Any).

@rstoyanchev, FYI.

Comment From: snicoll

Thanks @IIShabalin.

@leoshusar type erasure is indeed the problem here.