Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

While methods are made private in Java to prevent them from being called from outside the owning class, we may still need to invoke them for some reason.

To achieve this, we need to work around Java's access controls. This may help us reach a corner of a library or allow us to test some code that should normally remain private.

In this short tutorial, we'll look at how we can verify the functionality of a method regardless of its visibility. We'll consider two different approaches: the Java Reflection API and Spring's ReflectionTestUtils.

2. Visibility Out of Our Control

For our example, let's use a utility class LongArrayUtil that operates on long arrays. Our class has two indexOf methods:

public static int indexOf(long[] array, long target) {
    return indexOf(array, target, 0, array.length);
}

private static int indexOf(long[] array, long target, int start, int end) {
    for (int i = start; i < end; i++) {
        if (array[i] == target) {
            return i;
        }
    }
    return -1;
}

Let's assume that the visibility of these methods cannot be changed, and yet we want to call the private indexOf method.

3. Java Reflection API

3.1. Finding the Method with Reflection

While the compiler prevents us from calling a function that is not visible to our class, we can invoke functions via reflection. First, we need to access the Method object that describes the function we want to call:

Method indexOfMethod = LongArrayUtil.class.getDeclaredMethod(
  "indexOf", long[].class, long.class, int.class, int.class);

We have to use getDeclaredMethod in order to access non-private methods. We call it on the type that has the function, in this case, LongArrayUtil, and we pass in the types of the parameters to identify the correct method.

The function may fail and throw an exception if the method does not exist.

3.2. Allow the Method to Be Accessed

Now we need to elevate the method's visibility temporarily:

indexOfMethod.setAccessible(true);

This change will last until the JVM stops, or the accessible property is set back to false.

3.3. Invoke the Method with Reflection

Finally, we call invoke on the Method object:

int value = (int) indexOfMethod.invoke(
  LongArrayUtil.class, someLongArray, 2L, 0, someLongArray.length);

We have now successfully accessed a private method.

The first argument to invoke is the target object, and the remaining arguments need to match our method's signature. As in this case, our method is static, and the target object is the parent class – LongArrayUtil. For calling instance methods, we'd pass the object whose method we're calling.

We should also note that invoke returns Object, which is null for void functions, and which needs casting to the right type in order to use it.

4. Spring ReflectionTestUtils

Reaching internals of classes is a common problem in testing. Spring's test library provides some shortcuts to help unit tests reach classes. This often solves problems specific to unit tests, where a test needs to access a private field which Spring might instantiate at runtime.

First, we need to add the spring-test dependency in our pom.xml:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.4</version>
    <scope>test</scope>
</dependency>

Now we can use the invokeMethod function in ReflectionTestUtils, which uses the same algorithm as above, and saves us writing as much code:

int value = ReflectionTestUtils.invokeMethod(
  LongArrayUtil.class, "indexOf", someLongArray, 1L, 1, someLongArray.length);

As this is a test library, we wouldn't expect to use this outside of the test code.

5. Considerations

Using reflection to bypass function visibility comes with some risks and may not even be possible. We ought to consider:

  • Whether the Java Security Manager will allow this in our runtime
  • Whether the function we're calling, without compile-time checking, will continue to exist for us to call in the future
  • Refactoring our own code to make things more visible and accessible

6. Conclusion

In this article, we looked at how to access private methods using the Java Reflection API and using Spring's ReflectionTestUtils.

As always, the example code for this article can be found over on GitHub.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Comments are closed on this article!