Reflection in Java is a powerful feature that allows a program to examine or introspect its own structure at runtime.

It enables the program to inspect and manipulate classes, interfaces, fields, methods, and constructors dynamically, without knowing their names at compile time.

Using reflection, we can do the following at runtime :

  • Obtain class information : get information about a class, such as its name, super class, implemented interfaces, and modifiers;
  • Create instances : create objects of classes dynamically, even if the class name is not known until runtime;
  • Access fields : access and modify the fields (variables) of a class, regardless of their access level (public, private, protected, or default);
  • Invoke methods: invoke methods on objects, even if the method names are not known at compile time.

Reflection is primarily used in frameworks, libraries, and tools that need to work with unknown classes or provide extensibility points.

However, it’s important to note that while reflection can be a powerful tool, it should be used judiciously, as it may lead to reduced performance and can make the code less readable and maintainable.

What You Need

  • About 10 minutes
  • A favorite text editor or IDE
  • Java 8 or later

5. The Reflection API and Security Manager

The Reflection API allows the implementation of powerful functionalities.

This is one of the reasons why it is frequently used by many frameworks including Spring or Hibernate.

However, certain functionalities can also be used for malicious purposes which can harm the security of an application (invocation of methods, modification of the value of fields, etc. even if the modifiers of these members do not normally allow their access).

Access to an object using the Reflection API is done using an implementation of the AccessibleObject interface.

To bypass accessibility checks for an object’s elements, we must set the access property to true using the setAccessible() method.

However, this does not deactivate the checks carried out by the SecurityManager, if one is activated.

By default, no SecurityManager is enabled in a JVM.

To activate one, it must either :

  • Use the -Djava.security.manager option when launching the JVM;
  • Create a new instance of type SecurityManager() and pass it as a parameter to the setSecurityManager method of the System class.

When SecurityManager is activated on a JVM, it is necessary to authorize the ReflectPermission suppressAccessChecks to be able to use Reflection API functionalities.

If this permission is not given, then an exception is thrown when using these features.

 1import java.lang.reflect.InvocationTargetException;
 2import java.lang.reflect.Method;
 3
 4public class ClassTest17 {
 5    public static void main(String[] args)
 6            throws SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
 7            IllegalAccessException {
 8        System.setSecurityManager(new SecurityManager());
 9
10        Person p = new Person("tom", 18);
11
12        Class<?> clazz = p.getClass();
13
14        Method method = clazz.getDeclaredMethod("speak", String.class);
15
16        method.setAccessible(true);
17
18        method.invoke(p, "hello");
19    }
20}
21
22class Person {
23    private String name;
24    private int age;
25
26    public Person(String name, int age) {
27        this.name = name;
28        this.age = age;
29    }
30
31    @Override
32    public String toString() {
33        return "Person [name=" + name + ", age=" + age + "]";
34    }
35
36    private void speak(String words) {
37        System.out.println(this + " : " + words);
38    }
39}

The output of above code snippet is below :

Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")
        at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:485)
        at java.base/java.security.AccessController.checkPermission(AccessController.java:1068)
        at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:416)
        at java.base/java.lang.reflect.AccessibleObject.checkPermission(AccessibleObject.java:91)
        at java.base/java.lang.reflect.Method.setAccessible(Method.java:192)
        at ClassTest17.main(ClassTest17.java:16)

To grant the suppressAccessChecks permission to java.lang.reflect.ReflectPermission class, it is possible to define a .policy file which contains the definition of the security policy to apply.

grant { 
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
}; 

Then we need to specify the file as the value of the java.security.policy property of the JVM.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassTest18 {
    public static void main(String[] args)
            throws SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
            IllegalAccessException {
        System.setProperty("java.security.policy",
                "file:/home/yan/github/BlogTests/java.reflection/my_security.policy");
        System.setSecurityManager(new SecurityManager());

        Person p = new Person("tom", 18);

        Class<?> clazz = p.getClass();

        Method method = clazz.getDeclaredMethod("speak", String.class);

        method.setAccessible(true);

        method.invoke(p, "hello");
    }
}

Below is the output of above code snippet :

Person [name=tom, age=18] : hello

6. Using Reflection API on annotations

Annotations allow you to add metadata to Java source code.

This metadata can be exploited in source code, at compile time or at runtime using the Reflection API.

It allows access to annotations defined on a type, method, field or parameter dynamically at runtime.

6.1 Annotations on a class

As defined, the annotation can be used on a type (a class or an interface).

It is possible to use the Reflection API to dynamically access the annotation used on a class.

The getAnnotation() method of the Class class allows to obtain an element’s annotation for the specified type if such an annotation is present.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class ClassTest19 {
    public static void main(String[] args) {
        Person p = new Person("tom", 18);

        System.out.println(p);

        Class<?> clazz = p.getClass();
        toStringable annotation = clazz.getAnnotation(toStringable.class);

        System.out.println(
                "Person [" + annotation.name() + "=" + p.name + ", " + annotation.age() + "=" + p.age + "]");
    }

    @toStringable(name = "personName", age = "personAge")
    private static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    private static @interface toStringable {
        public String name();

        public String age();
    }
}

The output of above code snippet is below :

Person [personName=tom, personAge=18]

The getAnnotations() method of the Class class allows to obtain an array of type Annotation which contains all the annotations defined on the class.

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class ClassTest20 {
    public static void main(String[] args) {
        Person p = new Person("tom", 18);

        System.out.println(p);

        Class<?> clazz = p.getClass();
        Annotation[] annotations = clazz.getAnnotations();
        ;

        for (Annotation annotation : annotations) {
            if (annotation instanceof toStringable) {
                System.out.println(
                        "Person [" + ((toStringable) annotation).name() + "=" + p.name + ", "
                                + ((toStringable) annotation).age() + "=" + p.age + "]");
            }
        }
    }

    @toStringable(name = "personName", age = "personAge")
    private static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    private static @interface toStringable {
        public String name();

        public String age();
    }
}

Below is the output of above code snippet :

Person [personName=tom, personAge=18]

6.2 Annotations on a method

It is possible to use the Reflection API to dynamically access the annotations used on a method.

The getDeclaredAnnotation() method of the Method class allows to obtain an Annotation type instance encapsulating the annotation used on the method whose type is passed as a parameter.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassTest21 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        Person p = new Person("tom", 18);

        Class<?> clazz = p.getClass();

        Method method = clazz.getDeclaredMethod("toString", null);

        Greeting greeting = method.getDeclaredAnnotation(Greeting.class);

        System.out.println(method.invoke(p, null));
        System.out.println(greeting.value());
    }

    private static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        @Greeting("hello, how are you?")
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    private static @interface Greeting {
        public String value();
    }
}

The output of above code snippet is below :

Person [name=tom, age=18]
hello, how are you?

The getDeclaredAnnotations() method of the Method class allows to obtain an Annotation type array that contains all the annotations defined on the method.

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassTest22 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        Person p = new Person("tom", 18);

        Class<?> clazz = p.getClass();

        Method method = clazz.getDeclaredMethod("toString", null);

        Annotation[] annotations = method.getDeclaredAnnotations();

        for (Annotation annotation : annotations) {
            if (annotation instanceof Greeting) {
                System.out.println(method.invoke(p, null));
                System.out.println(((Greeting) annotation).value());
            }
        }
    }

    private static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        @Greeting("hello, how are you?")
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    private static @interface Greeting {
        public String value();
    }
}

Below is the output of above code snippet :

Person [name=tom, age=18]
hello, how are you?

6.3 Annotations on a method parameter

It is possible to use the Reflection API to dynamically access annotations used on method parameters.

The getParameterAnnotations() method returns a two-dimensional array of type Annotation which contains, for each parameter, the annotations associated with it.

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassTest23 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        Person p = new Person("tom", 18);

        Class<?> clazz = p.getClass();

        Method method = clazz.getDeclaredMethod("speak", String.class);

        for (Annotation[] annotations : method.getParameterAnnotations()) {
            for (Annotation annotation : annotations) {
                if (annotation instanceof Punctuation) {
                    String punctuation = ((Punctuation) annotation).value();

                    method.invoke(p, "hello world");
                    System.out.print(punctuation);
                }
            }
        }
    }

    private static class Person {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public void speak(@Punctuation(".\n") String words) {
            System.out.print(this + " : " + words);
        }

        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    private static @interface Punctuation {
        public String value();
    }
}

The output of above code snippet is below :

Person [name=tom, age=18] : hello world.

6.4 Annotations on a field

It is possible to use the Reflection API to dynamically access the annotations used on a field.

The getDeclaredAnnotations() method of the Field class allows to obtain an array of type Annotation which contains all the annotations defined on the field.

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class ClassTest24 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
        Person p = new Person("tom", 18);

        Class<?> clazz = p.getClass();

        Field name = clazz.getDeclaredField("name");

        for (Annotation annotation : name.getDeclaredAnnotations()) {
            if (annotation instanceof Alias) {
                String alias = ((Alias) annotation).value();
                System.out.println(alias + " = " + name.get(p));
            }
        }

        Field age = clazz.getDeclaredField("age");

        for (Annotation annotation : age.getDeclaredAnnotations()) {
            if (annotation instanceof Alias) {
                String alias = ((Alias) annotation).value();
                System.out.println(alias + " = " + age.get(p));
            }
        }
    }

    private static class Person {
        @Alias("person_name")
        private String name;
        @Alias("person_age")
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    private static @interface Alias {
        public String value();
    }
}

The output of above code snippet is below :

person_name = tom
person_age = 18

The getDeclaredAnnotation() method of the Field class allows to obtain an Annotation type instance encapsulating the annotation used on the field whose type is passed as a parameter.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class ClassTest25 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
        Person p = new Person("tom", 18);

        Class<?> clazz = p.getClass();

        Field name = clazz.getDeclaredField("name");

        Alias alias = name.getDeclaredAnnotation(Alias.class);
        System.out.println(alias.value() + " = " + name.get(p));

        Field age = clazz.getDeclaredField("age");

        alias = age.getDeclaredAnnotation(Alias.class);
        System.out.println(alias.value() + " = " + age.get(p));
    }

    private static class Person {
        @Alias("person_name")
        private String name;
        @Alias("person_age")
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    private static @interface Alias {
        public String value();
    }
}

Below is the output of above code snippet :

person_name = tom
person_age = 18