
Faster Java Reflection Alternative
- Transfer
Hello. Today we want to share with you a translation of an article prepared especially for students of the Java Developer course .
In my Specification Pattern article, I did not specifically mention the underlying component that helped a lot with the implementation. Here I will talk more about the JavaBeanUtil class that I used to get the value of an object field. In that example, it was FxTransaction .

Of course, you will say that you can use Apache Commons BeanUtils to get the same result .or one of its alternatives. But I was interested in delving into this and what I learned works much faster than any library built on the basis of the well-known Java Reflection .
A technology that avoids very slow reflection is bytecode instruction
In a nutshell, the technique I'm going to describe below uses LambdaMetafactory and MethodHandle to dynamically create an implementation of the Function interface . Function is the only method that delegates a call to the actual target method with code defined inside the lambda.
In this case, the target method is a getter that has direct access to the field we want to read. Also, I must say that if you are well acquainted with the innovations that appeared in Java 8, then you will find the code snippets below quite simple. Otherwise, the code may seem complicated at first sight.
The method below
To improve performance, I cache a function created dynamically, which in reality will read the value from the field with the name
The method
If you look closely at the creation of the function, then we need to go through the fields in the
The above method
Creating a tuple is performed with the method
In the two above methods, I use a constant with a name
Note that for setters you can use a similar approach using BiFunction instead of Function .
To measure performance, I used the wonderful JMH tool ( Java Microbenchmark Harness ), which is likely to be part of JDK 12 ( Translator's note: yes, jmh is included in java 9 ). As you may know, the result is platform dependent, so for reference: I will use
For comparison, I chose the Apache Commons BeanUtils library , widely known to most Java developers, and one of its alternatives called Jodd BeanUtil , which is claimed to be nearly 20% faster .
The benchmark code is as follows:
The benchmark defines four scenarios for different levels of nesting of the field. For each field, JMH will perform 5 iterations of 3 seconds to warm up, and then 5 iterations of 1 second for the actual measurement. Each scenario will be repeated 3 times to obtain better measurements.
Let's start with the results compiled for the JDK

Oracle JDK 8u191
The worst-case scenario using the approach
Now let's see how the same test works with

GraalVM EE 1.0.0-rc9
Full results can be seen here with the beautiful JMH Visualizer.
Such a big difference was due to the fact that the JIT compiler knows
If you're curious and want to delve deeper, I encourage you to grab the code from my Github repository . Keep in mind, I do not advise you to make home-made
On this the translation came to an end, and we invite everyone to June 13 toA free webinar , in which we will consider how a Docker can be useful for a Java developer: how to make a docker image with a java application and how to interact with it.
In my Specification Pattern article, I did not specifically mention the underlying component that helped a lot with the implementation. Here I will talk more about the JavaBeanUtil class that I used to get the value of an object field. In that example, it was FxTransaction .

Of course, you will say that you can use Apache Commons BeanUtils to get the same result .or one of its alternatives. But I was interested in delving into this and what I learned works much faster than any library built on the basis of the well-known Java Reflection .
A technology that avoids very slow reflection is bytecode instruction
invokedynamic
. In short, the manifestation invokedynamic
(or “indy”) was the most significant innovation in Java 7, which paved the way for implementing dynamic languages on top of the JVM using dynamic method invocations. Later, in Java 8, it is also possible to implement lambda expressions and references to methods (method reference), as well as to improve string concatenation in Java 9.In a nutshell, the technique I'm going to describe below uses LambdaMetafactory and MethodHandle to dynamically create an implementation of the Function interface . Function is the only method that delegates a call to the actual target method with code defined inside the lambda.
In this case, the target method is a getter that has direct access to the field we want to read. Also, I must say that if you are well acquainted with the innovations that appeared in Java 8, then you will find the code snippets below quite simple. Otherwise, the code may seem complicated at first sight.
Take a look at the makeshift JavaBeanUtil
The method below
getFieldValue
is a utility method used to read values from a JavaBean field. It takes a JavaBean object and a field name. The field name can be simple (for example fieldA
) or nested, separated by dots (for example, nestedJavaBean.nestestJavaBean.fieldA
).private static final Pattern FIELD_SEPARATOR = Pattern.compile("\\.");
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final ClassValue
To improve performance, I cache a function created dynamically, which in reality will read the value from the field with the name
fieldName
. In the method getCachedFunction
, as you can see, there is a “fast” path that uses ClassValue for caching, and a “slow” path createAndCacheFunction
that runs if the value in the cache is not found. The method
createFunctions
is a method that returns a list of functions that will be converted to string using Function::andThen
. Linking functions with each other in a chain can be represented as nested calls similar to getNestedJavaBean().getNestJavaBean().getNestJavaBean().getFieldA()
. After that, we simply cache the function by calling the method cacheAndGetFunction
. If you look closely at the creation of the function, then we need to go through the fields in the
path
following way:private static List createFunctions(Class javaBeanClass, String path) {
List functions = new ArrayList<>();
Stream.of(FIELD_SEPARATOR.split(path))
.reduce(javaBeanClass, (nestedJavaBeanClass, fieldName) -> {
Tuple2 getFunction = createFunction(fieldName, nestedJavaBeanClass);
functions.add(getFunction._2);
return getFunction._1;
}, (previousClass, nextClass) -> nextClass);
return functions;
}
private static Tuple2 createFunction(String fieldName, Class javaBeanClass) {
return Stream.of(javaBeanClass.getDeclaredMethods())
.filter(JavaBeanUtil::isGetterMethod)
.filter(method -> StringUtils.endsWithIgnoreCase(method.getName(), fieldName))
.map(JavaBeanUtil::createTupleWithReturnTypeAndGetter)
.findFirst()
.orElseThrow(IllegalStateException::new);
}
The above method
createFunctions
for each field fieldName
and class in which it is declared calls a method createFunction
that searches for the desired getter using javaBeanClass.getDeclaredMethods()
. Once the getter is found, it is converted to a Tuple tuple (Tuple from the Vavr library ), which contains the type returned by the getter, and a dynamically created function that will behave as if it were a getter itself. Creating a tuple is performed with the method
createTupleWithReturnTypeAndGetter
in combination with the method createCallSite
as follows:private static Tuple2 createTupleWithReturnTypeAndGetter(Method getterMethod) {
try {
return Tuple.of(
getterMethod.getReturnType(),
(Function) createCallSite(LOOKUP.unreflect(getterMethod)).getTarget().invokeExact()
);
} catch (Throwable e) {
throw new IllegalArgumentException("Lambda creation failed for getterMethod (" + getterMethod.getName() + ").", e);
}
}
private static CallSite createCallSite(MethodHandle getterMethodHandle) throws LambdaConversionException {
return LambdaMetafactory.metafactory(LOOKUP, "apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
getterMethodHandle, getterMethodHandle.type());
}
In the two above methods, I use a constant with a name
LOOKUP
that is just a reference to MethodHandles.Lookup . With it, I can create a direct link to a method (direct method handle) based on a previously found getter. And finally, the created MethodHandle is passed to the method createCallSite
in which the lambda body is created for the function using LambdaMetafactory . From there, ultimately, we can get an instance of CallSite , which is the “custodian” of the function. Note that for setters you can use a similar approach using BiFunction instead of Function .
Benchmark
To measure performance, I used the wonderful JMH tool ( Java Microbenchmark Harness ), which is likely to be part of JDK 12 ( Translator's note: yes, jmh is included in java 9 ). As you may know, the result is platform dependent, so for reference: I will use
1x6 i5-8600K 3,6 ГГц и Linux x86_64, а также Oracle JDK 8u191 и GraalVM EE 1.0.0-rc9
. For comparison, I chose the Apache Commons BeanUtils library , widely known to most Java developers, and one of its alternatives called Jodd BeanUtil , which is claimed to be nearly 20% faster .
The benchmark code is as follows:
@Fork(3)
@Warmup(iterations = 5, time = 3)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class JavaBeanUtilBenchmark {
@Param({
"fieldA",
"nestedJavaBean.fieldA",
"nestedJavaBean.nestedJavaBean.fieldA",
"nestedJavaBean.nestedJavaBean.nestedJavaBean.fieldA"
})
String fieldName;
JavaBean javaBean;
@Setup
public void setup() {
NestedJavaBean nestedJavaBean3 = NestedJavaBean.builder().fieldA("nested-3").build();
NestedJavaBean nestedJavaBean2 = NestedJavaBean.builder().fieldA("nested-2").nestedJavaBean(nestedJavaBean3).build();
NestedJavaBean nestedJavaBean1 = NestedJavaBean.builder().fieldA("nested-1").nestedJavaBean(nestedJavaBean2).build();
javaBean = JavaBean.builder().fieldA("fieldA").nestedJavaBean(nestedJavaBean1).build();
}
@Benchmark
public Object invokeDynamic() {
return JavaBeanUtil.getFieldValue(javaBean, fieldName);
}
/**
* Reference: http://commons.apache.org/proper/commons-beanutils/
*/
@Benchmark
public Object apacheBeanUtils() throws Exception {
return PropertyUtils.getNestedProperty(javaBean, fieldName);
}
/**
* Reference: https://jodd.org/beanutil/
*/
@Benchmark
public Object joddBean() {
return BeanUtil.declared.getProperty(javaBean, fieldName);
}
public static void main(String... args) throws IOException, RunnerException {
Main.main(args);
}
}
The benchmark defines four scenarios for different levels of nesting of the field. For each field, JMH will perform 5 iterations of 3 seconds to warm up, and then 5 iterations of 1 second for the actual measurement. Each scenario will be repeated 3 times to obtain better measurements.
results
Let's start with the results compiled for the JDK
8u191
: 
Oracle JDK 8u191
The worst-case scenario using the approach
invokedynamic
is much faster than the fastest of the other two libraries. This is a huge difference, and if you doubt the results, you can always download the source code and play with it as you like. Now let's see how the same test works with
GraalVM EE 1.0.0-rc9.

GraalVM EE 1.0.0-rc9
Full results can be seen here with the beautiful JMH Visualizer.
Observations
Such a big difference was due to the fact that the JIT compiler knows
CallSite
well MethodHandle
and can inline them, in contrast to the reflection approach. In addition, you can see how promising GraalVM is . Its compiler does really awesome work that can significantly improve reflection performance. If you're curious and want to delve deeper, I encourage you to grab the code from my Github repository . Keep in mind, I do not advise you to make home-made
JavaBeanUtil
to use it in production. My goal is simply to show my experiment and the possibilities that we can get from invokedynamic
. On this the translation came to an end, and we invite everyone to June 13 toA free webinar , in which we will consider how a Docker can be useful for a Java developer: how to make a docker image with a java application and how to interact with it.