-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
If I have something like this:
@Repository
internal class ContractDomainRepositoryImpl (
private val contractRepositoryImpl: ContractRepositoryImpl,
private val contractDomainEntityMapper: ContractDomainEntityMapperImpl,
) : ContractDomainRepository,
ContractDomainPostRepository by ContractDomainPostRepositoryDelegate(
contractRepositoryImpl,
contractDomainEntityMapper
),
ContractDomainPutRepository by ContractDomainPutRepositoryDelegate(
contractRepositoryImpl,
contractDomainEntityMapper
),
ContractDomainDeleteRepository by ContractDomainDeleteRepositoryDelegate(
contractRepositoryImpl
)
And the interface for ContractDomainPutRepository looks something like:
interface ContractDomainPutRepository {
suspend fun updateContract(
contract: Contract,
operationalData: ContractOperationalData,
userId: UserId,
organisationId: OrganisationId?,
projectId: ProjectId?,
): Pair<Contract, ContractOperationalData>
}
And UserId is a value class, like:
@JvmInline
value class UserId(override val value: Long) : Identifier<Long> {
init {
// TODO() here it is ok for UserId to be 0, for testing purposes only. Adjust later!
if (value < 0) {
throw ValidationError(
message = "UserId must be a positive value",
)
}
}
},
then trying to call ContractDomainPutRepository.updateContract (from the service layer, that injects ContractDomainRepositoryImpl) will not work
I keep getting Exception caught in ContractDomainServiceImpl: java.lang.IllegalArgumentException: argument type mismatch
Removing the nullable Value Classes, like:
interface ContractDomainPutRepository {
suspend fun updateContract(
contract: Contract,
operationalData: ContractOperationalData,
userId: UserId,
organisationId: OrganisationId,
projectId: ProjectId,
): Pair<Contract, ContractOperationalData>
},
makes the issue go away
But I need some of value classes to be nullable
The root cause I believe is that Spring's CGLIB proxying doesn't work well with Kotlin value classes since they're implemented as inline classes at compile time and have special JVM representations.
LLM Claude
If you LLM Claude with: "Value classes result it wrong type mismatch error in java class when passed as parameter in spring annotated bean. A plain class works fine. Explain". It says:
When Spring tries to:
Create proxies (CGLIB or JDK proxies)
Inject dependencies
Call methods from Java code
It sees the mangled signatures and underlying primitive types, not the value class wrapper. This causes type mismatches.
Key Takeaway
Value classes are optimized for performance by avoiding object allocation, but this optimization conflicts with Spring's reflection-heavy, proxy-based dependency injection, especially when Java interop is involved. For Spring beans, regular classes are safer unless you carefully manage where value classes are used.