JavaRuntype is a compact Java library that provides a runtime representation of the Java type system. It lets you model, build, inspect and compare Java types (including generics, bounds and arrays) at runtime in a convenient, type-safe way.
This is particularly useful for metadata-driven systems and frameworks, where you need to reason about types
beyond raw classes, and you want to include generics semantics. Java’s Class cannot carry generic parameters
at runtime, and while java.lang.reflect.Type offers the JVM view of type signatures, it is not ergonomic for
use as a first-class metadata model. JavaRuntype fills that gap with a more practical API.
All Type and TypeDef objects are immutable, and therefore thread-safe. They
are also serializable. All caches in the library are thread-safe and non-blocking.
JavaRuntype is open-source and distributed under the Apache License 2.0.
Note
This library is a new incarnation of the previous org.javaruntype library which could be previously found
at javaruntype. This new library starts at version 2.x, it is a
complete rewrite of the previous one, and it is not backward compatible.
JavaRuntype requires Java 17.
Library dependency: io.arxila.javaruntype:javaruntype:{version}
From Maven:
<dependency>
<groupId>io.arxila.javaruntype</groupId>
<artifactId>javaruntype</artifactId>
<version>{version}</version>
</dependency>JavaRuntype models the Java type system through two primary abstractions:
- Type: an instantiated, fully resolved type (optionally with array dimensions). Examples:
List<String>,List<? extends Serializable>,Map<String, List<Integer>>,String[][]. - TypeDef: a type definition blueprint derived from a class declaration (with type variables and bounds intact).
Examples:
List<E>,Map<K,V>,LinkedHashMap<K,V>.
Types are immutable and therefore thread-safe. They can be compared, serialized and used to determine assignability with precise generics semantics.
Class: io.arxila.javaruntype.type.Type
A Type object represents an instantiation of a Type Definition. For example:
java.util.List<java.lang.String>is an instantiation of theListType Definition, resolving theEtype parameter as thejava.lang.StringType.java.util.List<? extends java.io.Serializable>is another instantiation of theListType Definition, this time resolving theEtype parameter as an upper bound with typejava.io.Serializable.
Type objects are mainly obtained through the Types class (io.arxila.javaruntype.type.Types), and with the help
of the TypeParameters (io.arxila.javaruntype.type.TypeParameters) class:
//
// "Types.listOf(...)" is a static utility method in Types.
// "Types.STRING" is a static constant for the java.lang.String Type.
//
// listOfStringType = "java.util.List<java.lang.String>"
//
Type<List<String>> listOfStringType = Types.listOf(Types.STRING);
// For this type, we need to create an upper bound
Type<List<? extends Serializable>> listOfUnknownExtSerializableType =
Types.listOf(TypeParameters.forExtendsType(Types.SERIALIZABLE));
// List<? extends Serializable> is assignable from List<String>
assertTrue(listOfUnknownExtSerializableType.isAssignableFrom(listOfStringType));Class: io.arxila.javaruntype.typedef.TypeDef
A TypeDef object represents the declaration of a class or interface—the blueprint on which Type objects
are created and validated. It captures generics variable names and bounds as declared.
Some examples of TypeDefs:
java.util.List<E>is the TypeDef forjava.util.List.java.util.LinkedHashMap<K,V>is the TypeDef forjava.util.LinkedHashMap.
TypeDefs are mainly obtained via TypeDefs (io.arxila.javaruntype.typedef.TypeDefs):
// stringTypeDef = "java.lang.String"
TypeDef stringTypeDef = TypeDefs.forClass(String.class);
// listTypeDef = "java.util.List<E>"
TypeDef listTypeDef = TypeDefs.forClass(List.class);JavaRuntype provides several ways to obtain Type instances.
Types (io.arxila.javaruntype.type.Types) includes a comprehensive set of predefined constants for common types.
| Java Type | Constant |
|---|---|
java.lang.String |
Types.STRING |
java.lang.Integer |
Types.INTEGER |
java.util.Calendar |
Types.CALENDAR |
java.io.Serializable |
Types.SERIALIZABLE |
java.lang.Long[] |
Types.ARRAY_OF_LONG |
java.util.List<?> |
Types.LIST_OF_UNKNOWN |
java.util.List<String> |
Types.LIST_OF_STRING |
java.util.Set<Calendar> |
Types.SET_OF_CALENDAR |
java.util.Map<?,?> |
Types.MAP_OF_UNKNOWN_UNKNOWN |
java.util.Map<String,String> |
Types.MAP_OF_STRING_STRING |
java.util.Map<String,Boolean> |
Types.MAP_OF_STRING_BOOLEAN |
…and many more.
Raw type from class:
Type<String> strType = Types.forClass(String.class);
// ...
Type<List<?>> listOfUnkType = Types.forClass(List.class);Parameterized type from class:
Type<List<String>> listOfStrType =
Types.forClass(
List.class,
TypeParameters.forType(Types.STRING));
// ...
Type<Map<String,? extends Number>> mapOfStrExtNumberType =
Types.forClass(
Map.class,
TypeParameters.forType(Types.STRING),
TypeParameters.forExtendsType(Types.NUMBER));Types can be parsed from canonical or short names. For brevity, classes in java.lang, java.util, java.math
and java.io can omit their package names:
Type<String> strType = (Type<String>) Types.forName("String");
// ...
Type<Map<String,? extends Number>> mapOfStrExtNumberType =
(Type<Map<String,? extends Number>>) Types.forName("Map<String,? extends Number>");
// Fully-qualified when necessary
Type urlType = (Type<URL>) Types.forName("java.net.URL");
Type<String> strType = (Type<String>) Types.forName("String");Types can be built from reflection Type instances (e.g., Method#getGenericReturnType()):
Method aMethod = SomeClassOfMine.class.getMethod("toString");
java.lang.reflect.Type javaLangReflectType = aMethod.getGenericReturnType();
Type<String> strType = (Type<String>) Types.forJavaLangReflectType(javaLangReflectType);Variable substitution when generics include unresolved variables:
public class DataClass { public <E> Map<String,List<E>> getData() { /* ... */ return null; } }
// ...
Method getDataMethod = DataClass.class.getMethod("getData");
java.lang.reflect.Type returnType = getDataMethod.getGenericReturnType();
// Resolve E -> Integer
Map<String,Type<?>> variables = new HashMap<>();
variables.put("E", Types.INTEGER);
// type will be "Map<String,List<Integer>>"
Type<?> type = Types.forJavaLangReflectType(returnType, variables);
// Or using a cast, if we prefer to be exact:
Type<Map<String,List<Integer>>> preciseType = (Type<Map<String,List<Integer>>>) Types. forJavaLangReflectType(returnType, variables);Types provides factories to build commonly parameterized shapes from component types (both with Type<T> and
with TypeParameter<T> for wildcards/bounds).
| Transformation | Factory Method |
|---|---|
T → T[] |
Types.arrayOf(Type<T>) |
T → Iterable<T> |
Types.iterableOf(Type<T>) / Types.iterableOf(TypeParameter<T>) |
T → Class<T> |
Types.classOf(Type<T>) / Types.classOf(TypeParameter<T>) |
T → Collection<T> |
Types.collectionOf(Type<T>) / Types.collectionOf(TypeParameter<T>) |
T → Comparator<T> |
Types.comparatorOf(Type<T>) / Types.comparatorOf(TypeParameter<T>) |
T → Enumeration<T> |
Types.enumerationOf(Type<T>) / Types.enumerationOf(TypeParameter<T>) |
T → Iterator<T> |
Types.iteratorOf(Type<T>) / Types.iteratorOf(TypeParameter<T>) |
T → List<T> |
Types.listOf(Type<T>) / Types.listOf(TypeParameter<T>) |
T → ListIterator<T> |
Types.listIteratorOf(Type<T>) / Types.listIteratorOf(TypeParameter<T>) |
K,V → Map<K,V> |
Types.mapOf(Type<K>,Type<V>) / Types.mapOf(TypeParameter<K>,TypeParameter<V>) |
K,V → Map.Entry<K,V> |
Types.mapEntryOf(Type<K>,Type<V>) / Types.mapEntryOf(TypeParameter<K>,TypeParameter<V>) |
T → Queue<T> |
Types.queueOf(Type<T>) / Types.queueOf(TypeParameter<T>) |
T → Set<T> |
Types.setOf(Type<T>) / Types.setOf(TypeParameter<T>) |
Example:
Type<String[]> strArrType = Types.arrayOf(Types.STRING);Types also offers utility extractors to get component types from parameterized types:
| Transformation | Factory Method |
|---|---|
T[] → T |
Types.arrayComponentOf(Type<T[]>) |
Iterable<T> → T |
Types.iterableComponentOf(Type<Iterable<T>>) |
Class<T> → T |
Types.classComponentOf(Type<Class<T>>) |
Collection<T> → T |
Types.collectionComponentOf(Type<Collection<T>>) |
Comparator<T> → T |
Types.comparatorComponentOf(Type<Comparator<T>>) |
Enumeration<T> → T |
Types.enumerationComponentOf(Type<Enumeration<T>>) |
Iterator<T> → T |
Types.iteratorComponentOf(Type<Iterator<T>>) |
List<T> → T |
Types.listComponentOf(Type<List<T>>) |
ListIterator<T> → T |
Types.listIteratorComponentOf(Type<ListIterator<T>>) |
Map<K,V> → K |
Types.mapKeyComponentOf(Type<Map<K,V>>) |
Map<K,V> → V |
Types.mapValueComponentOf(Type<Map<K,V>>) |
Map.Entry<K,V> → K |
Types.mapEntryKeyComponentOf(Type<Map.Entry<K,V>>) |
Map.Entry<K,V> → V |
Types.mapEntryValueComponentOf(Type<Map.Entry<K,V>>) |
Queue<T> → T |
Types.queueComponentOf(Type<Queue<T>>) |
Set<T> → T |
Types.setComponentOf(Type<Set<T>>) |
Type<String> strType = Types.arrayComponentOf(Types.ARRAY_OF_STRING);