Redesign `ResolvedType` to be a generic interface that does not implement `Type`
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 25/100
Línea de trabajo
Empieza leyendo la implementación actual de ResolvedType y las discusiones en #37, #69 y #76; después, compara la dirección propuesta con el ResolvedTypeMapper de PR #103. El trabajo estará terminado cuando el proyecto acuerde el diseño de una interfaz genérica, su puente Type, las implementaciones, las factories y el alcance de la migración para una futura versión major.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
@cowtowncoder I'm in the middle of creating an abstract database interface that involves transparent storage of objects (initially Java record) with full type conversions. For a corner of the conversion I'm using Jackson ObjectMapper, although I'm not sure about what I will use long-term. In any case I had a chance to dig into my old PLOOP code (described in #37) and how it relates to ResolvedType (see e.g. our discussion in #69). There are several tensions lurking in those discussions, as well as the proposal I made in #76 that GenericType<T> should not really be a java.lang.reflect.Type.
For this current work I just now had a long and in-depth conversation with one of the top LLMs, refreshing my memory about these issues and understanding what it would look like to bring PLOOP up to date and carry it forward using ClassMate. The benefit here is that in our discussion we started with actual, practical, working code that we had just implemented in this database framework. We looked at how our new implementation could be generalized by bringing in PLOOP+ClassMate. We came upon some important insights, which I believe form a synthesis of the tensions I was feeling in those other tickets, especially in light of the new PR #103 which basically tries to reverse a ResolvedType back into a Type. Below is a summary (produced by the LLM) of those insights. I leave a few additional comments at the end.
ClassMate ResolvedType Design Insights
Background
This builds on discussions from #37 (PLOOP properties layer, 2017), #69 (ResolvedType → JavaType conversion), and the draft PR #103 (ResolvedTypeMapper). These issues circle around the same fundamental design tension in ClassMate's type abstraction.
The Core Problem
ResolvedType currently implements java.lang.reflect.Type, which appears to offer seamless interoperability. However, Type has subtype interfaces (ParameterizedType, GenericArrayType, TypeVariable, WildcardType) that code routinely inspects:
Type type = ...;
if (type instanceof ParameterizedType pt) {
Type[] args = pt.getActualTypeArguments();
// process generic arguments
}
ResolvedType doesn't implement these subtypes. Code that receives a ResolvedType through a Type reference and checks instanceof ParameterizedType gets false—silently losing all generic information. This affects Jackson's TypeFactory, Hibernate Validator, and any API that inspects Type structure.
PR #103 addresses this by creating a ResolvedTypeMapper that synthesizes proper ParameterizedType and GenericArrayType implementations from ResolvedType data. This works, but it's an external workaround for what should be built-in behavior.
ResolvedType as a Type Replacement
The insight from working with these APIs is that ResolvedType should be understood as a replacement for Type in application code, not an implementation of it.
Within an application, you'd use ResolvedType everywhere—passing it through property descriptors, converters, serializers. You'd only touch Type at boundaries:
- Input: Converting from
Field.getGenericType(),RecordComponent.getGenericType(), etc. - Output: Converting to
Typewhen calling external APIs (Jackson, Hibernate, etc.)
This positions ResolvedType as a richer abstraction that shields application code from Type's design limitations while remaining interoperable.
Why ResolvedType Should Be Generic
Java's reflection types aren't generic—ParameterizedType.getRawType() returns Type, not Type<T>. This forces runtime casts and loses compile-time safety:
Type listType = field.getGenericType(); // List<String>
Class<?> raw = getRawType(listType);
Object result = raw.cast(someValue); // no compile-time safety
A generic ResolvedType<T> would preserve type safety through the chain:
ResolvedType<List<String>> listType = resolver.resolve(field);
Class<List<String>> raw = listType.erasedClass();
List<String> result = raw.cast(someValue); // type-safe
This matters for frameworks passing type information through multiple layers.
Why ResolvedType Should Be an Interface
ClassMate's value is resolving type variables through inheritance hierarchies. But not all contexts need that resolution:
record User(String id, List<String> tags) {}
For records, RecordComponent.getGenericType() returns fully-specified types. ClassMate resolution adds overhead without benefit.
If ResolvedType were an interface, there could be multiple implementations:
- Simple wrapper: Stores the original
Typedirectly;toType()returns it unchanged - Resolution result: ClassMate-backed;
toType()synthesizes aTypefrom resolved data
Both satisfy the same interface. Consumers don't know or care which implementation they receive.
The toType() Bridge
Rather than implementing Type (which fails instanceof checks), ResolvedType should provide an explicit toType() method that returns a proper Type hierarchy:
- For simple wrappers: return the stored original
Type - For resolution results: synthesize
ParameterizedTypeImpl,GenericArrayTypeImpl, etc. (as PR #103 does)
The synthesized types implement the correct interfaces, so instanceof ParameterizedType works as expected. External APIs receive types they can inspect normally.
This is essentially what PR #103 provides externally. Building it into ResolvedType would make it a first-class capability.
Proposed Design Sketch
public interface ResolvedType<T> {
Class<T> erasedClass();
List<ResolvedType<?>> typeArguments();
Optional<ResolvedType<?>> typeArgument(int index);
boolean isParameterized();
boolean isArray();
Optional<ResolvedType<?>> arrayComponentType();
Type toType(); // original or synthesized
// Factory methods
static <T> ResolvedType<T> of(Class<T> clazz) { ... }
static ResolvedType<?> of(Type type) { ... } // simple wrapper, stores original
static ResolvedType<?> resolve(Type type, Class<?> context) { ... } // full resolution
}
Key design points:
- Interface, not abstract class—allows implementation flexibility
- Generic
<T>parameter—preserves compile-time type safety - Does not implement
java.lang.reflect.Type—avoidsinstanceoffailures toType()method—explicit bridge for external API interoperability- Multiple factory methods—simple wrapping vs. full resolution
Summary
ResolvedType currently occupies an awkward middle ground: implementing Type suggests compatibility, but the subtype contracts aren't satisfied. PR #103 demonstrates that users need proper Type representations for external APIs, but the current design forces external workarounds.
The proposed redesign clarifies ResolvedType's role: a richer, type-safe replacement for Type within application code, with an explicit toType() bridge for interoperability. Making it an interface enables both lightweight wrappers (preserving original types) and full resolution results (synthesizing types), unified under a common abstraction.
This raises lots of questions which we can discuss in the comments. e.g. Is ResolvedType the best name for this new, redesigned thing? Are you interested in going in this direction for a new major ClassMate version?
For the short term in my framework I will create this general, improved ResolvedType interface (probably with a different name), with factories that produce it. The early iterations may have ClassMate and the current ResolvedType serving as backing implementations, depending on how much this helps go forward quickly. Longer term I would hope that this interface on our end could be replaced with a redesigned ClassMate ResolvedType, whatever its name; and/or integrated with PLOOP.
I would also be interested in discussing the possibility of working with you for a new ClassMate that might even have a separate property description facility mentioned in #37 and possibly even used eventually in a new version of Jackson. (With LLMs such large-scale thinking and refactoring suddenly becomes much more feasible, even producing better results than if humans were to painstakingly pore over the code—as long as innovative, design-oriented humans direct and guide the LLMs.)
- Lenguaje dominante
- Java
- Estrellas
- 266
- Forks
- 47
- Merge medio
- 1 h 42 min
- PR fusionados (30 d)
- 1
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de FasterXML/java-classmate
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 20/100
FasterXML/java-classmate#81 · 1 comentario ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
FasterXML/java-classmate#76 · 6 comentarios · 1 reacción ·
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 20/100
FasterXML/java-classmate#72 · 3 comentarios ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
FasterXML/java-classmate#69 · 22 comentarios ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
FasterXML/java-classmate#57 · 1 comentario ·
Todos los issues de FasterXML/java-classmate
Issues similares
-
awaiting triage bug Causes friction Hop Gui P1 P2 Transforms
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
apache/flink-agents#1152 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
jenkinsci/blueocean-plugin#5417 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
objectionary/eo-graphs#75 ·