Skip to content
Anjunar/ DOCS

Caching and identity

TypeResolver keeps one ResolvedClass per Type for the life of the program. Resolving is cheap after the first time, and the model grows lazily as you use it.

Identity

The same type, the same instance

resolve looks the Type up in a map first. Two ResolvedClasses are equal when their underlying types are equal.

scala
val first = TypeResolver.resolve(classOf[User]) val second = TypeResolver.resolve(classOf[User]) println(first eq second) // true
Laziness

Paid on first access

hierarchy, fields, methods, constructors and annotations are lazy vals. The first call reads the JVM's reflection data, every later call returns the cached arrays.

Good to know
The cache is never cleared; resolved types live as long as the class loader.
companionClass keeps its own cache, including the misses.
BeanIntrospector and AnnotationIntrospector cache one model per class (and annotation).
Scala 3 lazy vals are initialized once, even when several threads ask at the same time.
Hot deploys with a new class loader get new Types, and so new entries.
Threads

The cache map is not synchronized

In 1.0.4 the type cache, the companion cache and the introspector caches are plain hash maps without synchronization. Concurrent first-time resolves of different types can race on them.

Warm the cache on one thread

In a server, resolve the types you map during startup, before requests arrive, or serialize the first resolve yourself. Reading already cached types is safe as long as nothing writes at the same time.

scala
// at startup, before the HTTP server accepts requests Seq(classOf[User], classOf[Article], classOf[Comment]).foreach { clazz => val resolved = TypeResolver.resolve(clazz) resolved.fields; resolved.methods // touch the lazy parts once AnnotationIntrospector.create(resolved, classOf[JsonbProperty]).properties }