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 @RestControllers 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.