Understanding Java’s Reflection API in Five Minutes
A language that is capable of reflection, like Java is, allows developers to inspect types, methods, fields, annotations, etc. at run time and to defer the decision on how to use them from compile time to run time. To that end, Java’s reflection API offers types like
Annotation, and others. With them it is possible to interact with types that were not known at compile time, for example to create instances of an unknown class and call methods on them.
This quick tip is intended to give you a high-level understanding of what reflection is, what it looks like in Java, and what it could be used for. After it you will be ready to get started or work through longer tutorials. To get the most out of it you should have a good understanding of how Java is structured, specifically what classes and methods are and how they relate. Knowing about annotations unlocks a separate section.
Instead of building from the ground up I want to start with a simple example. First as plain Java code:
URL url = new URL("https://sitepoint.com/java"); String urlString = url.toExternalForm(); System.out.println(urlString);
I decided at compile time (meaning, when I was writing the code) that I wanted to create an
URL object, and call some method in it. Here’s how I would do the same with Java’s reflection API:
// the gateway to reflection is the `Class` instance // for the class you want to operate on Class<?> type = Class.forName("java.net.URL"); // fetches the constructor that takes a `String` argument // and uses it to create a new instance for the given string Constructor<?> constructor = type.getConstructor(String.class); Object instance = constructor.newInstance("https://sitepoint.com/java"); // fetches the `toExternalForm` method and invokes it on // the instance that was just created Method method = type.getMethod("toExternalForm"); Object methodCallResult = method.invoke(instance); System.out.println(methodCallResult);
Using the reflection API is of course more cumbersome than writing the code directly. But this way, details that used to be baked into the code (like that I use
URL or which method I call) becomes just a parameter. As a consequence, instead of having to settle on
toExternalForm at compile time, they could be decided upon later when the program is already running.
Most use cases for this occur in “frameworky” environments. Think about JUnit, for example, that wants to execute all methods that are annotated with
@Test. Once it found them with a class path scan it uses
invoke to call them. Spring and other web frameworks act similarly when looking for controllers and request mappings. Extensible applications that want to load user-provided plugins at run time are another use case.
Fundamental Types and Methods
The gateway into the reflection API is
Class::forName. In its simple form this static method just takes a fully qualified class name and returns a
Class instance for it. That instance can be used to get fields, methods, constructors, and more.
To get a specific constructor, the
getConstructor method can be called with the types of the constructor arguments as I have done above. Similarly, specific methods can be accessed by calling
getMethod and passing its name as well as the parameter types. The
getMethod("toExternalForm") call above did not specify any types because the method has no arguments.
Here’s a method that does:
Class<?> type = Class.forName("java.net.URL"); // `URL::openConnection` has an overload that accepts a java.net.Proxy Method openConnection = type.getMethod("openConnection", Proxy.class);
The instances returned by these calls are of type
Method, respectively. To call the underlying member they offer methods like
Method::invoke. An interesting detail of the latter is that the instance on which the method is to be called needs to be passed to it as the first argument. The other arguments will be passed on to the called method.
If a static method is to be called, the instance argument will be ignored and can hence be
Annotations are an important part of reflection. In fact, annotations are primarily targeted towards reflection. They are meant to provide meta information that can be accessed at run time and then used to shape the behavior of the program. (As mentioned, JUnit’s
@Test and Spring’s
@RequestMapping are great examples.)
All important reflection related types like
Parameter implement the
AnnotatedElement interface. The linked Javadoc contains a thorough explanation of how annotations can relate to these elements (directly present, indirectly present, or associated) but it’s simplest form is this: The
getAnnotations method returns the annotations present on that element in form of an array of
Annotation instances, whose members can then be accessed.
Java’s reflection API allows the introspection of types, methods, annotations, etc. at run time and the invocation of constructors and methods that were not known at compile time. To get started, call
Class.forName("fully.qualified.class.Name") and then
getAnnotations, or similar methods. Invocation happens with
newInstance for constructors and
invoke for methods.
You can also use reflection to break into code and mutate non-public fields or call non-public methods – this is a risky practice, though, and gets even tougher to pull off in Java 9. If you’re curious and would like to know all the API’s ins and outs, give the reflection trail in Java’s official documentation a go. By now, the API is a little dated and has some downsides but newer alternatives exist, check out method handles (since Java 7) and variable handles (since Java 9).