Logo Dark

A Complete Guide to Java Reflection

19 February 2025

Tech

Table of contents

Ever wanted to take a look inside a Java program when it’s running? Perhaps observe what’s happening with an object, or even modify its behavior? This is the power Java Reflections gives you. It is a way to have fun with your classes, fields, methods, and more during the program execution.

In this blog, we will see how Java Reflection works, what its strengths and weaknesses of it, and how we can see it in production use cases such MapStruct and more. 

So, let’s get started!

What is Java Reflection?

What is Reflection in Java→ Reflection in Java is a feature that allows you to inspect and manipulate classes and objects at runtime. You do not need to know the specifics of a class at compile time to use it.

Java Reflection allows for dynamic class inspection and manipulation, but to truly harness its power, you may want to hire Java developers who can expertly integrate it into your projects for maximum efficiency.

While reflection in Java is a powerful tool, it does not come without its pros and cons. Here are the pros and cons, to give you some insights into when and how to use it.

Pros of Java Reflection

Flexibility

Reflection enables you to manipulate classes and objects without having to know exactly what they are in advance. You may create objects, acquire information, or call methods during the program execution.

Useful for Frameworks

The Spring and Hibernate frameworks use reflection to initialize things like object connections without us having to wire them explicitly. Reflection allows the framework to do all of this without having to write extra code.

See Inside Classes

While your program is running, you can view a class and see what fields, methods, or constructors it has. This can give you a sense of how things are to help when debugging.

Access Class Private Parts

By default, private fields or methods are inaccessible by other classes. Reflection allows you to tap into these hidden parts which helps when testing or debugging.

Cons of Java Reflection

Performance Overhead

Reflection causes your program to run more slowly because the runtime has to spend additional time checking multiple things at runtime, such as class details. This can become a problem when you're doing a lot of reflection in a large application.

Security Problems

Reflection Java can be through private fields and methods which could compromise security. If an attacker is abusing reflection, they can break into parts of your program that you're hoping are closed off.

Can Cause Errors

Since reflection operates at runtime, it can introduce errors that do not manifest until after the program has started running. For instance, trying to access a nonexistent field or method could crash your program.

Harder to Understand

Reflection Java -based code can be difficult to read and comprehend. It’s not always obvious what is going on, especially if you or someone else is going to have to maintain the code in the future.

Practical Examples

In this section, we will explore some practical examples of how Java reflection examples can be used to streamline CRUD operations, helping to automate and simplify tasks in large-scale applications.

Accessing Variables at Runtime

Here’s an example that illustrates how to access and modify a variable at runtime using reflection:

 import java.lang.reflect.Field;
 public class ReflectionExample {
   public String name = "Reflection";
   public static void main(String[] args) throws Exception {
      ReflectionExample example = new ReflectionExample();
      Field field = example.getClass().getField("name");
      /* Accessing the value of the field*/
      String value = (String) field.get(example);
      System.out.print("Value of 'name': " + value);
      // Value of 'name': Reflection
      /* Modifying the value of the field*/
      field.set(example, "Java Reflection");
      System.out.print("Modified value of 'name': "+example.name);
      // Modified value of 'name': Java Reflection
   }
 }

So we get the “ReflectionExample” class name - we access its field called name - and we get its value. Next, we make a change and we see the new value. This shows how to grab the class properties dynamically.

Getting the Names of All Fields of a Class

To get the fields (variables) declared in a class, use the following:

 import java.lang.reflect.Field;
 public class ReflectionFieldsExample {
   public String field1;
   private int field2;
   protected boolean field3;
   public static void main(String[] args) {
      Class<?> clazz = ReflectionFieldsExample.class;
      // Getting all fields of the class
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
         System.out.println("Field name: " + field.getName());
      }
   }
 }

Here, we use the getDeclaredFields method, which will return all the fields of a class, including private, protected, and default access fields. That’s useful when you want to take a look at a class’s structure.

Getting the Constructor of the Class

Understanding how to get a constructor and call it at runtime:

 import java.lang.reflect.Constructor;
 public class ReflectionConstructorExample {
   public ReflectionConstructorExample() {
      System.out.println("Default Constructor");
   }
   public ReflectionConstructorExample(String param) {
      System.out.println("Constructor with parameter: " + param);
   }
   public static void main(String[] args) throws Exception {
      Class<?> clazz = ReflectionConstructorExample.class;
      // Getting all constructors of the class
      Constructor<?>[] constructors = clazz.getConstructors();
      for (Constructor<?> constructor : constructors) {
         System.out.println("Constructor: " + constructor);
         // Invoking constructors
         if (constructor.getParameterCount() == 1) {
            constructor.newInstance("Hello");
         } else {
            constructor.newInstance();
         }
      }
   }
 }

This example retrieves all of the constructors from the ReflectionConstructorExample class and invokes them according to the number of parameters. This is an example of how to dynamically create an instance of a class.

How MapStruct Uses Java Reflection Concepts

MapStruct is a Java-based code generator that simplifies the mapping between Java bean types based on a convention-over-configuration approach. It carries out most of the work at compile-time but employs reflection concepts to dynamically understand and match fields.

Let's start with the following situations as an example:

If you have PersonDTO (Data Transfer Object) and PersonEntity. As a rule, you had to do a lot of coding to map every field. MapStruct simply requires you to define an interface:

 @Mapper
 public interface PersonMapper {
  PersonEntity toEntity(PersonDTO dto);
  PersonDTO toDto(PersonEntity entity);
 }

The mapping logic is generated for you by MapStruct. Internally, it matches up on field names and types using reflection concepts. All fields in PersonDTO, whose name matches the PersonEntity fields, MapStruct maps automatically.

This is a good demonstration of how reflection can help us to write less boilerplate code and thus make our application more malleable.

How Can Reflection in Java Take CRUD Operations to the Next Level?

Java Reflections is much more than just inspecting objects or invoking methods dynamically. You may even go as far as getting it to generate controllers, services, and repositories for CRUD (create, read, update, delete) operations directly from your database schema (DDL) automatically! 

So, assuming you are involved in a big project with different entities, this technique can save you a lot of time and effort. 

Let’s see how it works!

Step 1: Parse the Database Schema (DDL)

It all begins with parsing your database schema. For example, take this table definition:

CREATE TABLE User (
       id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
       username VARCHAR(255) NOT NULL,
       email VARCHAR(255) NOT NULL,
       password VARCHAR(255) NOT NULL,
       isActive BOOLEAN DEFAULT FALSE,
       otp VARCHAR(255),
       isVerified BOOLEAN DEFAULT FALSE
       );

Here we define a User entity and fields like id, username, email, etc. This schema will be parsed and the corresponding Java components will help it get created.

Step 2: Dynamically Generate Entity Classes

With Reflection, you can programmatically create entity classes out of the parsed schema. It eliminates the pain of having to define each field manually and provides consistency throughout all your classes.

Here’s how it works:

  1. Analyze the table structure.
  2. Create a Java class that contains fields for each table column.
  3. Include @Entity and @data and for PK use @Id

For example, the User table might lead to an entity class such as:

 @Getter
 @Setter
 @Entity
 public class User {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;
   private String username;
   private String email;
   private String password;
   private boolean isActive;
   private String otp;
   private boolean isVerified;
 }
]

This virtual entity maps directly to your database table. Consistency? Check. Time saved? Double-check!

Step 3: Generate Repositories, Services, and Controllers Automatically

Reflection Java also allows for the automatic generation of JpaRepositoty interfaces, service layers, and REST Controllers. CRUD operations — finding, saving, updating, or deleting entities — happen automatically without the need to write the often repetitious code, because these components are dynamically adapted to the schema.

Example: Auto-Generated Components

1. Repository:

 package com.mvc_code_generation.repository;
 import org.springframework.data.jpa.repository.JpaRepository;
 import com.mvc_code_generation.entity.User;
 public interface UserRepository extends JpaRepository<User,Long> {}

2. Service:

 @Service
 public class UserService {
   @Autowired
   private UserRepository repository;
   public List<User> getAll() {
       return repository.findAll();
   }
   public User getById(Long id) {
       return repository.findById(id).orElse(null);
   }
   public User save(User entity) {
       return repository.save(entity);
   }
   public User update(Long id, User entity) {
       if (repository.existsById(id)) {
           entity.setId(id);
           return repository.save(entity);
       } else {
           return null;
       }
   }
   public void delete(Long id) {
       repository.deleteById(id);
   }
 }

3. Controller:

 @RestController
 @RequestMapping("/user")
 public class UserController {
   @Autowired
   private UserService service;
   @GetMapping
   public List<User> getAll() {
       return service.getAll();
   }
   @GetMapping("/{id}")
   public User getById(@PathVariable Long id) {
       return service.getById(id);
   }
   @PostMapping
   public User create(@RequestBody User entity) {
       return service.save(entity);
   }
   @PutMapping("/{id}")
   public User update(@PathVariable Long id, @RequestBody User entity) {
       return service.update(id, entity);
   }
   @DeleteMapping("/{id}")
   public void delete(@PathVariable Long id) {
       service.delete(id);
   }
}

If you are generating these layers dynamically, you can remove a lot of boilerplate code and thus speed up and clean your development process.

With the Java reflection example, you can dynamically generate code for the CRUD operations based on your entity classes and database schema. 

This approach can significantly simplify your development process by automating repetitive tasks, ensuring consistency, and allowing for rapid iteration in large-scale projects.

Wrapping up

Bring excitement to what this is capable of doing. You can view the full implementation on GitHub, using Java Reflection in Spring Boot to generate MVC. This includes everything from parsing DDL to creating a full-blown CRUD layer using Java Reflection. It's a good way to take your coding skills to the next level! 💡

WRITTEN BY

Krupalee Suriya

Krupalee is an Assistant Software Engineer at 7Span, skilled in Java and React, they tackle real-world challenges while embracing lifelong learning, collaboration, and sharing insights with the tech community.

More from this author

Making IT Possible

Making IT Possible

Making IT Possible

Making IT Possible

Making IT Possible

Making IT Possible

India (HQ)

201, iSquare Corporate Park, Science City Road, Ahmedabad-380060, Gujarat, India

Canada

24 Merlot Court, Timberlea, NS B3T 0C2, Canada

For Sales

[email protected]

Looking For Jobs

Apply Now

LinkedIn
Instagram
X
Facebook
Youtube
Discord
Dribbble
Behance
Github