Zero‑Restart Hotfixes in Java: Using ByteBuddy for Runtime Class Modification
This article demonstrates how to apply ByteBuddy in Java 21 to create dynamic classes, delegate methods, define new methods and fields, and redefine existing classes at runtime, enabling urgent bug fixes and logic changes without restarting the application.
1. Introduction
In Java development, fixing urgent bugs or adjusting business logic without restarting the application is often difficult. Runtime class modification solves this problem, and ByteBuddy provides a powerful API to achieve it.
2. Environment
Java 21
3. Dependency
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.17.8</version>
</dependency>4. Creating a Dynamic Class
Using ByteBuddy we subclass Object, intercept the toString method and return a fixed string.
DynamicType.Unloaded<Object> unloadedType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.isToString())
.intercept(FixedValue.value("Hello ByteBuddy!"))
.make();
Class<?> dynamicType = unloadedType
.load(RuntimeCreateClassTest.class.getClassLoader())
.getLoaded();
String ret = dynamicType.getDeclaredConstructor().newInstance().toString();
System.err.println(ret);Running the code prints Hello ByteBuddy!. The explanation notes that subclass() creates a new subclass of Object, ElementMatchers.isToString() selects the toString method, and intercept() supplies a custom implementation.
5. Method Delegation (Custom Logic)
Two simple classes are defined:
public class Person {
public String say() { return "Person say"; }
}
public class Student {
public static String say() { return "Student say"; }
}ByteBuddy delegates Person.say() to Student.say():
Class<? extends Person> clazz = new ByteBuddy()
.subclass(Person.class)
.method(named("say").and(isDeclaredBy(Person.class)).and(returns(String.class)))
.intercept(MethodDelegation.to(Student.class))
.make()
.load(MethodDelegationCustomLogicTest.class.getClassLoader())
.getLoaded();
String ret = clazz.getDeclaredConstructor().newInstance().say();
System.err.println(ret);The output is Student say. ByteBuddy matches the target method by name, declaring class and return type.
6. Selecting Between Multiple Candidates
If the target class contains several methods with the same signature, ByteBuddy can use the @BindingPriority annotation to decide.
public class Student {
@BindingPriority(1)
public static String say1() { /* ... */ }
@BindingPriority(2)
public static String speek() { /* ... */ }
}Running the delegation now prints Student speek, showing that the method with the higher priority (2) is chosen.
7. Defining New Methods and Fields
ByteBuddy can also create a brand‑new class, add a method and a field, and intercept the method with another class.
Class<?> type = new ByteBuddy()
.subclass(Object.class)
.name("MyPackClass")
.defineMethod("run", String.class, Modifier.PUBLIC)
.intercept(MethodDelegation.to(Student.class))
.defineField("id", String.class, Modifier.PUBLIC)
.make()
.load(DefinitionMethodAndFieldTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Method m = type.getDeclaredMethod("run");
Object instance = type.getDeclaredConstructor().newInstance();
Object ret = m.invoke(instance);
System.err.println(ret);
System.err.println(type.getDeclaredField("id").get(instance));The method call prints Student speek and the newly defined field is null because it was never assigned.
8. Redefining an Existing Class
To modify a class that is already loaded, add the byte-buddy-agent dependency and install the agent.
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.17.8</version>
</dependency> ByteBuddyAgent.install();
new ByteBuddy()
.redefine(Person.class)
.method(named("say"))
.intercept(FixedValue.value("Person Value Redefined"))
.make()
.load(Student.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Person p = new Person();
String ret = p.say();
System.err.println(ret);The result is Person Value Redefined. The agent installs a Java agent into the running JVM, and redefine() replaces the implementation of the selected method.
9. Conclusion
The examples illustrate how ByteBuddy can be used for zero‑restart hot‑fixes: creating new classes, delegating methods, adding members, and redefining existing classes, all without stopping the application.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
