A Type-Safe UUID
in Kotlin
TL;DR: Create a value class with a type parameter, and use it everywhere except on the JPARepository
.
- Author:
- Christian Hujer, CEO / CTO at Nelkinda Software Craft Pvt Ltd
- First Published:
- by
Nelkinda Software Craft Private Limited
- Last Modified:
- by Christian Hujer
- Approximate reading time:
A type-safe UUID
, that would be nice, right? In Kotlin, I've done it! Here's how!
1 Motivation
Identifiers are everywhere. Identifiers help us to identify things by reference without passing a copy of the entire thing around. One of the most popular identifiers is the UUID
, available in Java through the java.util.UUID
class.
But this class has one problem. The UUID
does not know what it identifies. Say you write Spring code that books a cinema ticket. You might end up having a method that looks a bit like this: fun bookSeat(movie: UUID, user: UUID)
Whether the first UUID
identifies a movie or a user is not clear at the call-site. Point of this example is, that the UUIDs are not type-safe. It's easy to get them mixed up, and the compiler will not help you. Unless…
2 Behold: Type-safe UUIDs!
In Kotlin, we can use a parameterized value class
to create a type-safe UUID
. Here's how the code could look like:
@Suppress("unused") // TargetType used for type-checking @JvmInline value class UUID<TargetType>(val value: java.util.UUID) { companion object { @JvmStatic fun <TargetType> fromString(name: String) = UUID<TargetType>(java.util.UUID.fromString(name)) @JvmStatic fun <TargetType> randomUUID() = UUID<TargetType>(java.util.UUID.randomUUID()) } }
UUID
classHow to use it? Simply use it everywhere except at your JPARepository
. Why? Because Kotlin value classes and Hibernate don't play well together (yet?) in all situations.
Here's how to use it in an Entity:
@Entity data class Movie { @Id @get:JvmName("getId") val id: UUID<Movie>, // ... }
UUID
on an EntityNote the @get:JvmName("getId")
annotation. It is important to have a getter with the name getId
for Hibernate to work properly. Currently, usages of Kotlin value classes are always name-mangled unless the name is explicitly mentioned.
Here's how to (not) use it in a Repository:
interface MovieRepository : JpaRepository<Movie, java.util.UUID>
UUID
on a Repository: Don't, instead use java.util.UUID
directlyNote that the repository uses java.util.UUID
, not our new type-safe UUID
type. Hibernate has no support for Kotlin value-classes yet, and will not be able to find the entity by the type-safe UUID
.
And here's how to use it in a Service:
@Service class MovieService( @Autowired private val movieRepository: MovieRepository, ) { fun findById(id: UUID<Movie>) = movieRepository.findById(id.value) }
In the service, we "translate" from the type-safe UUID
to the "normal" java.util.UUID
of the repository that Hibernate understands.
It should be noted that the type-safe UUID
also works on @RestController
s and in Swagger without any issues.
3 Conclusion
Kotlin value classes are a great way to create type-safe UUIDs. Note that the support in Hibernate and elsewhere might not be perfect yet. Hence, the workaround to directly use java.util.UUID
on the JPARepository
.