eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

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

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

The use of the Reflection API has sparked extensive debates within the Java community over time and is sometimes seen as a bad practice. While it is widely used by popular Java frameworks and libraries, its potential drawbacks have discouraged its frequent use in regular server-side applications.

In this tutorial, we’ll delve into the benefits and drawbacks that reflection may introduce into our codebases. Additionally, we’ll explore when it is appropriate or inappropriate to use reflection, ultimately helping us determine whether it qualifies as a bad practice.

2. Understanding Java Reflection

In computer science, reflective programming or reflection is the ability of a process to examine, introspect, and modify its structure and behavior. When a programming language fully supports reflection, it permits the inspection and modification of the structure and behavior of classes and objects in the codebase at runtime, allowing the source code to rewrite aspects of itself.

According to this definition, Java offers full support for reflection. Apart from Java, other common programming languages that offer support for reflective programming are C#, Python, and JavaScript.

Many popular Java frameworks, like Spring and Hibernate, rely on it to provide advanced features like dependency injection, aspect-oriented programming, and database mapping. Apart from using reflection indirectly through frameworks or libraries, we can use it directly with the help of the java.lang.reflect package, or the Reflections library.

3. The Pros of Java Reflection

Java Reflection can be a powerful and versatile feature if used carefully. In this section, we’ll explore some of the key advantages of reflection and how we can use it effectively in certain scenarios.

3.1. Dynamic Configuration

Reflection API empowers dynamic programming, enhancing an application’s flexibility and adaptability. This aspect proves valuable when we encounter scenarios where required classes or modules are unknown until runtime.

Moreover, by making use of reflection’s dynamic capabilities, developers can build systems that can be reconfigured effortlessly in real-time, without the need for extensive code changes.

For example, the Spring framework uses reflection for creating and configuring beans. It scans classpath components and dynamically instantiates and configures beans based on annotations and XML configurations, allowing developers to add or modify beans without changing the source code.

3.2. Extensibility

Another significant advantage of using reflection is extensibility. This enables us to incorporate new functionalities or modules at runtime, without changing the application’s core code.

To illustrate that, let’s suppose we are using a third-party library that defines a base class and incorporates multiple sub-types for polymorphic deserialization. We would like to extend the functionality by introducing our own custom sub-types that extend the same base class. The Reflection API comes in handy for this particular use case because we can utilize it to dynamically register these custom sub-types at runtime and effortlessly integrate them with the third-party library. Thus, we can adapt a library to our specific requirements without altering its codebase.

3.3. Code Analysis

Another use case of reflection is code analysis, which allows us to inspect code dynamically. This is particularly useful because it can lead to enhanced quality of software development.

For example, ArchUnit, a Java library for architectural unit testing, makes use of reflection and bytecode analysis. Information that the library cannot obtain through the Reflection API is obtained at the bytecode level. This way, the library analyzes the code dynamically, and we’re able to enforce architectural rules and constraints, ensuring the integrity and high quality of our software projects.

4. The Cons of Java Reflection

As we saw in the previous section, reflection is a powerful feature with various applications. However, it comes with a set of drawbacks that we need to consider before we decide to use it in our projects. In this section, we’ll delve into some of the major cons of this feature.

4.1. Performance Overhead

Java reflection dynamically resolves types and may limit certain JVM optimizations. Consequently, reflective operations have slower performance than their non-reflective counterparts. So, when dealing with performance-sensitive applications, we should consider avoiding reflection in parts of the code that are called frequently.

To demonstrate this, we’re going to create a very simple Person class and perform some reflective and non-reflective operations on it:

public class Person {

    private String firstName;
    private String lastName;
    private Integer age;

    public Person(String firstName, String lastName, Integer age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    // standard getters and setters
}

Now, we can create a benchmark in order to see the time difference in calling the getters of our class:

public class MethodInvocationBenchmark {

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public void directCall(Blackhole blackhole) {

        Person person = new Person("John", "Doe", 50);

        blackhole.consume(person.getFirstName());
        blackhole.consume(person.getLastName());
        blackhole.consume(person.getAge());
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public void reflectiveCall(Blackhole blackhole) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {

        Person person = new Person("John", "Doe", 50);

        Method getFirstNameMethod = Person.class.getMethod("getFirstName");
        blackhole.consume(getFirstNameMethod.invoke(person));

        Method getLastNameMethod = Person.class.getMethod("getLastName");
        blackhole.consume(getLastNameMethod.invoke(person));

        Method getAgeMethod = Person.class.getMethod("getAge");
        blackhole.consume(getAgeMethod.invoke(person));
    }
}

Let’s check the results of running our method invocation benchmark:

Benchmark                                 Mode  Cnt    Score   Error  Units
MethodInvocationBenchmark.directCall      avgt    5    8.428 ± 0.365  ns/op
MethodInvocationBenchmark.reflectiveCall  avgt    5  102.785 ± 2.493  ns/op

Now, let’s create another benchmark to test the performance of reflective initialization compared to directly calling the constructor:

public class InitializationBenchmark {

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public void directInit(Blackhole blackhole) {

        blackhole.consume(new Person("John", "Doe", 50));
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public void reflectiveInit(Blackhole blackhole) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        Constructor<Person> constructor = Person.class.getDeclaredConstructor(String.class, String.class, Integer.class);
        blackhole.consume(constructor.newInstance("John", "Doe", 50));
    }
}

Let’s check our results for constructor invocation:

Benchmark                                 Mode  Cnt    Score   Error  Units
InitializationBenchmark.directInit        avgt    5    5.290 ± 0.395  ns/op
InitializationBenchmark.reflectiveInit    avgt    5   23.331 ± 0.141  ns/op

After reviewing the results of both benchmarks, we can reasonably infer that using reflection in Java can be considerably slower for use cases like invoking methods or initializing objects.

Our article Microbenchmarking with Java has more information about what we’ve used for comparing the execution times.

4.2. Exposure of Internals

Reflection permits operations that might be restricted in non-reflective code. A good example is the ability to access and manipulate private fields and methods of classes. By doing so, we violate encapsulation, a fundamental principle of object-oriented programming.

As an example, let’s create a dummy class with only one private field, without creating any getters or setters:

public class MyClass {
    
    private String veryPrivateField;
    
    public MyClass() {
        this.veryPrivateField = "Secret Information";
    }
}

Now, let’s try to access this private field in a unit test:

@Test
public void givenPrivateField_whenUsingReflection_thenIsAccessible()
  throws IllegalAccessException, NoSuchFieldException {
      MyClass myClassInstance = new MyClass();

      Field privateField = MyClass.class.getDeclaredField("veryPrivateField");
      privateField.setAccessible(true);

      String accessedField = privateField.get(myClassInstance).toString();
      assertEquals(accessedField, "Secret Information");
}

4.3. Loss of Compile-Time Safety

Another drawback of reflection is the loss of compile-time safety. In typical Java development, the compiler performs strict type checks and ensures that we’re using classes, methods, and fields correctly. However, reflection bypasses these checks, and as a result, some errors aren’t discoverable until runtime. Therefore, this can lead to hard-to-detect bugs and may compromise the reliability of our codebase.

4.4. Decreased Maintainability of Code

Using reflection can significantly decrease code maintainability. Code that relies heavily on reflection tends to be less readable than non-reflective code. Reduced readability can lead to maintenance difficulties because it’s harder for developers to understand the code’s intent and functionality.

Another challenge is represented by limited tool support. Reflection isn’t fully supported by all development tools and IDEs. As a result, this can slow down development and make it more error-prone, as developers must rely on manual inspections to catch issues.

4.5. Security Concerns

Java reflection involves accessing and manipulating internal elements of the program, which can cause security concerns. In a restrictive environment, allowing reflective access can be risky because malicious code might attempt to exploit reflection to gain unauthorized access to sensitive resources or perform actions that violate security policies.

5. Impact of Java 9 on Reflection

The introduction of modules in Java 9 brought significant changes to the way modules encapsulate their code. Before Java 9, encapsulation could have been broken easily using reflection.

Modules no longer expose their internals by default. However, Java 9 provided some mechanisms to selectively grant permissions for reflective access between modules. This allows us to open specific packages when necessary, ensuring compatibility with legacy code or third-party libraries.

6. When Should We Use Java Reflection?

Having explored the advantages and disadvantages of reflection, we can identify some use cases when it would be appropriate or not to use this powerful feature.

Using the Reflection API proves valuable where dynamic behavior is essential. As we’ve already seen, many well-known frameworks and libraries, such as Spring and Hibernate, rely on it for key features. In these cases, reflection enables these frameworks to offer flexibility and customization to developers. Additionally, when we’re creating a library or framework ourselves, reflection can empower other developers to extend and customize their interactions with our code, making it a suitable choice.

Furthermore, reflection can be an option for extending code we can’t modify. Therefore, it can be a powerful tool when we’re working with third-party libraries or legacy code and need to integrate new functionalities or adapt existing ones without altering the original codebase. It allows us to access and manipulate elements that would otherwise be inaccessible, making it a practical choice for such scenarios.

However, it’s important to exercise caution when considering using reflection. In applications with strong security requirements, using reflective code should be approached carefully. Reflection allows access to internal elements of a program, which can potentially be exploited by malicious code. Additionally, when dealing with performance-critical applications, particularly in sections of code that are frequently called, reflection’s performance overhead can become a concern. Moreover, if compile-time type checking is crucial for our project, we should consider avoiding using reflective code because of its lack of compile-time safety.

7. Conclusion

As we’ve learned throughout this article, reflection in Java should be viewed as a powerful tool that demands careful use, rather than being labeled as a bad practice. Similar to any feature, excessive use of reflection can indeed be considered a bad practice. However, when applied carefully and only when genuinely necessary, reflection can be a valuable asset.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

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

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments