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. Introduction

In this article, we’ll look at how to use the ASM library for manipulating an existing Java class by adding fields, adding methods, and changing the behavior of existing methods.

2. Dependencies

We need to add the ASM dependencies to our pom.xml:

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>6.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>6.0</version>
</dependency>

We can get the latest versions of asm and asm-util from Maven Central.

3. ASM API Basics

The ASM API provides two styles of interacting with Java classes for transformation and generation: event-based and tree-based.

3.1. Event-based API

This API is heavily based on the Visitor pattern and is similar in feel to the SAX parsing model of processing XML documents. It is comprised, at its core, of the following components:

  • ClassReader – helps to read class files and is the beginning of transforming a class
  • ClassVisitor – provides the methods used to transform the class after reading the raw class files
  • ClassWriter – is used to output the final product of the class transformation

It’s in the ClassVisitor that we have all the visitor methods that we’ll use to touch the different components (fields, methods, etc.) of a given Java class. We do this by providing a subclass of ClassVisitor to implement any changes in a given class.

Due to the need to preserve the integrity of the output class concerning Java conventions and the resulting bytecode, this class requires a strict order in which its methods should be called to generate correct output.

The ClassVisitor methods in the event-based API are called in the following order:

visit
visitSource?
visitOuterClass?
( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd

3.2. Tree-based API

This API is a more object-oriented API and is analogous to the JAXB model of processing XML documents.

It’s still based on the event-based API, but it introduces the ClassNode root class. This class serves as the entry point into the class structure.

4. Working With the Event-based ASM API

We’ll modify the java.lang.Integer class with ASM. And we need to grasp a fundamental concept at this point: the ClassVisitor class contains all the necessary visitor methods to create or modify all the parts of a class.

We only need to override the necessary visitor method to implement our changes. Let’s start by setting up the prerequisite components:

public class CustomClassWriter {

    static String className = "java.lang.Integer"; 
    static String cloneableInterface = "java/lang/Cloneable";
    ClassReader reader;
    ClassWriter writer;

    public CustomClassWriter() {
        reader = new ClassReader(className);
        writer = new ClassWriter(reader, 0);
    }
}

We use this as a basis to add the Cloneable interface to the stock Integer class, and we also add a field and a method.

4.1. Working With Fields

Let’s create our ClassVisitor that we’ll use to add a field to the Integer class:

public class AddFieldAdapter extends ClassVisitor {
    private String fieldName;
    private String fieldDefault;
    private int access = org.objectweb.asm.Opcodes.ACC_PUBLIC;
    private boolean isFieldPresent;

    public AddFieldAdapter(
      String fieldName, int fieldAccess, ClassVisitor cv) {
        super(ASM4, cv);
        this.cv = cv;
        this.fieldName = fieldName;
        this.access = fieldAccess;
    }
}

Next, let’s override the visitField method, where we first check if the field we plan to add already exists and set a flag to indicate the status.

We still have to forward the method call to the parent class — this needs to happen as the visitField method is called for every field in the class. Failing to forward the call means no fields will be written to the class.

This method also allows us to modify the visibility or type of existing fields:

@Override
public FieldVisitor visitField(
  int access, String name, String desc, String signature, Object value) {
    if (name.equals(fieldName)) {
        isFieldPresent = true;
    }
    return cv.visitField(access, name, desc, signature, value); 
}

We first check the flag set in the earlier visitField method and call the visitField method again, this time providing the name, access modifier, and description. This method returns an instance of FieldVisitor.

The visitEnd method is the last method called in order of the visitor methods. This is the recommended position to carry out the field insertion logic.

Then, we need to call the visitEnd method on this object to signal that we’re done visiting this field:

@Override
public void visitEnd() {
    if (!isFieldPresent) {
        FieldVisitor fv = cv.visitField(
          access, fieldName, fieldType, null, null);
        if (fv != null) {
            fv.visitEnd();
        }
    }
    cv.visitEnd();
}

It’s important to be sure that all the ASM components used come from the org.objectweb.asm package — a lot of libraries use the ASM library internally and IDEs could auto-insert the bundled ASM libraries.

We now use our adapter in the addField method, obtaining a transformed version of java.lang.Integer with our added field:

public class CustomClassWriter {
    AddFieldAdapter addFieldAdapter;
    //...
    public byte[] addField() {
        addFieldAdapter = new AddFieldAdapter(
          "aNewBooleanField",
          org.objectweb.asm.Opcodes.ACC_PUBLIC,
          writer);
        reader.accept(addFieldAdapter, 0);
        return writer.toByteArray();
    }
}

We’ve overridden the visitField and visitEnd methods.

Everything to be done concerning fields happens with the visitField method. This means we can also modify existing fields (say, transforming a private field to the public) by changing the desired values passed to the visitField method.

4.2. Working With Methods

Generating whole methods in the ASM API is more involved than other operations in the class. This involves a significant amount of low-level byte-code manipulation and, as a result, is beyond the scope of this article.

For most practical uses, however, we can either modify an existing method to make it more accessible (perhaps make it public so that it can be overridden or overloaded) or modify a class to make it extensible.

Let’s make the toUnsignedString method public:

public class PublicizeMethodAdapter extends ClassVisitor {
    public PublicizeMethodAdapter(int api, ClassVisitor cv) {
        super(ASM4, cv);
        this.cv = cv;
    }
    public MethodVisitor visitMethod(
      int access,
      String name,
      String desc,
      String signature,
      String[] exceptions) {
        if (name.equals("toUnsignedString0")) {
            return cv.visitMethod(
              ACC_PUBLIC + ACC_STATIC,
              name,
              desc,
              signature,
              exceptions);
        }
        return cv.visitMethod(
          access, name, desc, signature, exceptions);
   }
}

Like we did for the field modification, we merely intercept the visit method and change the parameters we desire.

In this case, we use the access modifiers in the org.objectweb.asm.Opcodes package to change the visibility of the method. We then plug in our ClassVisitor:

public byte[] publicizeMethod() {
    pubMethAdapter = new PublicizeMethodAdapter(writer);
    reader.accept(pubMethAdapter, 0);
    return writer.toByteArray();
}

4.3. Working With Classes

Along the same lines as modifying methods, we modify classes by intercepting the appropriate visitor method. In this case, we intercept visit, which is the very first method in the visitor hierarchy:

public class AddInterfaceAdapter extends ClassVisitor {

    public AddInterfaceAdapter(ClassVisitor cv) {
        super(ASM4, cv);
    }

    @Override
    public void visit(
      int version,
      int access,
      String name,
      String signature,
      String superName, String[] interfaces) {
        String[] holding = new String[interfaces.length + 1];
        holding[holding.length - 1] = cloneableInterface;
        System.arraycopy(interfaces, 0, holding, 0, interfaces.length);
        cv.visit(V1_8, access, name, signature, superName, holding);
    }
}

We override the visit method to add the Cloneable interface to the array of interfaces to be supported by the Integer class. We plug this in just like all the other uses of our adapters.

5. Using the Modified Class

So we’ve modified the Integer class. Now we need to be able to load and use the modified version of the class.

In addition to simply writing the output of writer.toByteArray to disk as a class file, there are some other ways to interact with our customized Integer class.

5.1. Using the TraceClassVisitor

The ASM library provides the TraceClassVisitor utility class that we’ll use to introspect the modified class. Thus we can confirm that our changes have happened.

Because the TraceClassVisitor is a ClassVisitor, we can use it as a drop-in replacement for a standard ClassVisitor:

PrintWriter pw = new PrintWriter(System.out);

public PublicizeMethodAdapter(ClassVisitor cv) {
    super(ASM4, cv);
    this.cv = cv;
    tracer = new TraceClassVisitor(cv,pw);
}

public MethodVisitor visitMethod(
  int access,
  String name,
  String desc,
  String signature,
  String[] exceptions) {
    if (name.equals("toUnsignedString0")) {
        System.out.println("Visiting unsigned method");
        return tracer.visitMethod(
          ACC_PUBLIC + ACC_STATIC, name, desc, signature, exceptions);
    }
    return tracer.visitMethod(
      access, name, desc, signature, exceptions);
}

public void visitEnd(){
    tracer.visitEnd();
    System.out.println(tracer.p.getText());
}

What we have done here is to adapt the ClassVisitor that we passed to our earlier PublicizeMethodAdapter with the TraceClassVisitor.

All the visiting will now be done with our tracer, which then can print out the content of the transformed class, showing any modifications we’ve made to it.

While the ASM documentation states that the TraceClassVisitor can print out to the PrintWriter that’s supplied to the constructor, this doesn’t appear to work properly in the latest version of ASM.

Fortunately, we have access to the underlying printer in the class and were able to manually print out the tracer’s text contents in our overridden visitEnd method.

5.2. Using Java Instrumentation

This is a more elegant solution that allows us to work with the JVM at a closer level via Instrumentation.

To instrument the java.lang.Integer class, we write an agent that will be configured as a command line parameter with the JVM. The agent requires two components:

  • A class that implements a method named premain
  • An implementation of ClassFileTransformer in which we’ll conditionally supply the modified version of our class
public class Premain {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(
              ClassLoader l,
              String name,
              Class c,
              ProtectionDomain d,
              byte[] b)
              throws IllegalClassFormatException {
                if(name.equals("java/lang/Integer")) {
                    CustomClassWriter cr = new CustomClassWriter(b);
                    return cr.addField();
                }
                return b;
            }
        });
    }
}

We now define our premain implementation class in a JAR manifest file using the Maven jar plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Premain-Class>
                    com.baeldung.examples.asm.instrumentation.Premain
                </Premain-Class>
                <Can-Retransform-Classes>
                    true
                </Can-Retransform-Classes>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

Building and packaging our code so far produces the jar that we can load as an agent. To use our customized Integer class in a hypothetical “YourClass.class“:

java YourClass -javaagent:"/path/to/theAgentJar.jar"

6. Conclusion

While we implemented our transformations here individually, ASM allows us to chain multiple adapters together to achieve complex transformations of classes.

In addition to the basic transformations we examined here, ASM also supports interactions with annotations, generics, and inner classes.

We’ve seen some of the power of the ASM library — it removes a lot of limitations we might encounter with third-party libraries and even standard JDK classes.

ASM is widely used under the hood of some of the most popular libraries (Spring, AspectJ, JDK, etc.) to perform a lot of “magic” on the fly.

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.

Course – LS – NPI (cat=Java)
announcement - icon

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

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)