How to Build a Spring Boot Hot Patch Loader for Instant Production Fixes

This article walks through the motivation, design, implementation, and best‑practice deployment of a Spring Boot hot‑patch system that lets developers dynamically load and apply patches to beans, classes, and methods in production within minutes, avoiding lengthy release cycles.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
How to Build a Spring Boot Hot Patch Loader for Instant Production Fixes

Background: Why Hot Patch?

Imagine a Friday night when a critical production interface throws a NullPointerException, affecting many users. Traditional release (modify code → test → package → deploy) takes 1‑2 hours, rolling back may lose new features, while a hot patch can fix the issue in minutes without service interruption.

Design Ideas

The hot‑patch loader is built on several core concepts:

Dynamic class loading : leverage Java ClassLoader to load patch classes at runtime.

Multi‑level replacement : support Spring Bean replacement, ordinary Java class replacement, and static method replacement.

Bytecode enhancement : use a Java Agent and the Instrumentation API to replace any class at runtime.

Version management : each patch carries a version number and can be rolled back.

Secure and controllable : only patches from allowed directories are accepted to prevent security risks.

Project Structure

springboot-hot-patch/
├── src/main/java/com/example/hotpatch/
│   ├── agent/               # Java Agent related
│   │   └── HotPatchAgent.java
│   ├── annotation/          # Annotation definitions
│   │   ├── HotPatch.java
│   │   └── PatchType.java
│   ├── config/              # Configuration classes
│   │   ├── HotPatchConfig.java
│   │   └── HotPatchProperties.java
│   ├── controller/         # REST controllers
│   │   ├── HotPatchController.java
│   │   └── TestController.java
│   ├── core/                # Core hot‑patch loader
│   │   └── HotPatchLoader.java
│   ├── example/             # Example code
│   │   ├── UserService.java
│   │   ├── StringUtils.java
│   │   └── MathHelper.java
│   ├── instrumentation/    # Bytecode manipulation
│   │   └── InstrumentationHolder.java
│   ├── model/               # Data models
│   │   ├── PatchInfo.java
│   │   └── PatchResult.java
│   └── patches/             # Patch examples
│       ├── UserServicePatch.java
│       ├── StringUtilsPatch.java
│       └── MathHelperDividePatch.java
├── src/main/resources/
│   ├── static/            # Web templates
│   │   └── index.html
│   └── application.properties
├── patches/                # Patch files directory
└── pom.xml

Maven Configuration

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>springboot-hot-patch</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <name>Spring Boot Hot Patch Loader</name>
    <description>A Spring Boot 3 based hot patch loader for runtime class replacement</description>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>3.2.0</spring-boot.version>
        <asm.version>9.5</asm.version>
        <micrometer.version>1.12.0</micrometer.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- ASM for bytecode manipulation -->
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-util</artifactId>
            <version>${asm.version}</version>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

Annotation and Enum Definitions

/**
 * Patch type enum
 */
public enum PatchType {
    /** Spring Bean replacement */
    SPRING_BEAN,
    /** Ordinary Java class replacement */
    JAVA_CLASS,
    /** Static method replacement */
    STATIC_METHOD,
    /** Instance method replacement */
    INSTANCE_METHOD
}

/**
 * Hot patch annotation
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HotPatch {
    PatchType type() default PatchType.SPRING_BEAN;
    String originalBean() default "";
    String originalClass() default "";
    String methodName() default "";
    String methodSignature() default "";
    String version() default "1.0";
    String description() default "";
    boolean securityCheck() default true;
}

Java Agent Support

/**
 * Instrumentation holder – provides access to JVM Instrumentation instance
 */
public class InstrumentationHolder {
    private static volatile Instrumentation instrumentation;
    public static void setInstrumentation(Instrumentation inst) { instrumentation = inst; }
    public static Instrumentation getInstrumentation() { return instrumentation; }
    public static boolean isAvailable() { return instrumentation != null; }
}

/**
 * Java Agent entry class
 */
public class HotPatchAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("HotPatch Agent started");
        InstrumentationHolder.setInstrumentation(inst);
    }
    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("HotPatch Agent dynamically attached");
        InstrumentationHolder.setInstrumentation(inst);
    }
}

Configuration Properties Class

/**
 * Hot patch configuration properties
 */
@ConfigurationProperties(prefix = "hotpatch")
@Component
@Data
public class HotPatchProperties {
    private boolean enabled = false;
    private String path = "./patches";
    private long maxFileSize = 10 * 1024 * 1024;
    private boolean signatureVerification = false;
    private List<String> allowedRoles = List.of("ADMIN", "DEVELOPER");
}

Data Models

/**
 * Patch information class
 */
@Data
@AllArgsConstructor
public class PatchInfo {
    private String name;
    private String version;
    private Class<?> patchClass;
    private PatchType patchType;
    private long loadTime;
    private String originalTarget;
    // constructor extracts originalTarget from annotation
}

/**
 * Patch operation result class
 */
@Data
@AllArgsConstructor
public class PatchResult {
    private boolean success;
    private String message;
    private Object data;
    public static PatchResult success(String msg) { return new PatchResult(true, msg, null); }
    public static PatchResult success(String msg, Object data) { return new PatchResult(true, msg, data); }
    public static PatchResult failed(String msg) { return new PatchResult(false, msg, null); }
}

Core Hot Patch Loader

@Component
@Slf4j
public class HotPatchLoader {
    private final ConfigurableApplicationContext applicationContext;
    private final HotPatchProperties properties;
    private final Map<String, PatchInfo> loadedPatches = new ConcurrentHashMap<>();
    private final Instrumentation instrumentation;
    public HotPatchLoader(ConfigurableApplicationContext ctx, HotPatchProperties props) {
        this.applicationContext = ctx;
        this.properties = props;
        this.instrumentation = InstrumentationHolder.getInstrumentation();
    }
    public PatchResult loadPatch(String patchName, String version) {
        if (!properties.isEnabled()) {
            return PatchResult.failed("Hot patch feature is disabled");
        }
        try {
            File patchFile = validatePatchFile(patchName, version);
            URLClassLoader patchClassLoader = createPatchClassLoader(patchFile);
            Class<?> patchClass = loadPatchClass(patchClassLoader, patchName);
            HotPatch patchAnnotation = patchClass.getAnnotation(HotPatch.class);
            if (patchAnnotation == null) {
                return PatchResult.failed("Patch class missing @HotPatch annotation");
            }
            PatchType patchType = patchAnnotation.type();
            switch (patchType) {
                case SPRING_BEAN:
                    replaceSpringBean(patchClass, patchAnnotation);
                    break;
                case JAVA_CLASS:
                    replaceJavaClass(patchClass, patchAnnotation);
                    break;
                case STATIC_METHOD:
                    replaceStaticMethod(patchClass, patchAnnotation);
                    break;
                case INSTANCE_METHOD:
                    return PatchResult.failed("Instance method replacement not implemented");
                default:
                    return PatchResult.failed("Unsupported patch type: " + patchType);
            }
            PatchInfo info = new PatchInfo(patchName, version, patchClass, patchType, System.currentTimeMillis());
            loadedPatches.put(patchName, info);
            log.info("Hot patch {}:{} ({}) loaded successfully", patchName, version, patchType);
            return PatchResult.success("Patch loaded successfully");
        } catch (Exception e) {
            log.error("Hot patch load failed: {}", e.getMessage(), e);
            return PatchResult.failed("Patch load failed: " + e.getMessage());
        }
    }
    // Additional methods: validatePatchFile, createPatchClassLoader, loadPatchClass,
    // replaceSpringBean, replaceJavaClass, replaceStaticMethod, rollbackPatch, getLoadedPatches, etc.
}

REST API Controller

@RestController
@RequestMapping("/api/hotpatch")
@Slf4j
public class HotPatchController {
    private final HotPatchLoader patchLoader;
    public HotPatchController(HotPatchLoader loader) { this.patchLoader = loader; }
    @PostMapping("/load")
    public ResponseEntity<PatchResult> loadPatch(@RequestParam String patchName, @RequestParam String version) {
        log.info("Request to load hot patch: {}:{}", patchName, version);
        PatchResult result = patchLoader.loadPatch(patchName, version);
        return ResponseEntity.ok(result);
    }
    @GetMapping("/list")
    public ResponseEntity<List<PatchInfo>> listPatches() {
        return ResponseEntity.ok(patchLoader.getLoadedPatches());
    }
    @PostMapping("/rollback")
    public ResponseEntity<PatchResult> rollbackPatch(@RequestParam String patchName) {
        log.info("Request to rollback patch: {}", patchName);
        PatchResult result = patchLoader.rollbackPatch(patchName);
        return ResponseEntity.ok(result);
    }
    @GetMapping("/status")
    public ResponseEntity<String> getStatus() { return ResponseEntity.ok("Hot Patch Loader is running"); }
}

Web Management UI (Simplified)

A Tailwind‑CSS based web page provides a dashboard to scan the patch directory, load patches via the REST API, view loaded patches with their version, type, load time, and target, and monitor statistics such as total patches, successful loads, and last load time.

Hot Patch UI
Hot Patch UI

Practical Examples

1. Spring Bean Replacement Example

@Service
public class UserService {
    public String getUserInfo(Long userId) {
        if (userId == null) return null; // bug
        if (userId == 1L) return "Alice";
        if (userId == 2L) return "Bob";
        return null; // bug
    }
    public int getUserNameLength(Long userId) {
        String userName = getUserInfo(userId);
        return userName.length(); // NPE when null
    }
}
@HotPatch(
    type = PatchType.SPRING_BEAN,
    originalBean = "userService",
    version = "1.0.1",
    description = "Fix NullPointerException in getUserInfo"
)
@Service
public class UserServicePatch {
    public String getUserInfo(Long userId) {
        if (userId == null) return "未知用户";
        if (userId == 1L) return "Alice";
        if (userId == 2L) return "Bob";
        return "未知用户";
    }
    public int getUserNameLength(Long userId) {
        String userName = getUserInfo(userId);
        return userName != null ? userName.length() : 0;
    }
}

2. Ordinary Java Class Replacement Example

// Original utility class
public class StringUtils {
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0; // ignores whitespace
    }
}
@HotPatch(
    type = PatchType.JAVA_CLASS,
    originalClass = "com.example.hotpatch.example.StringUtils",
    version = "1.0.2",
    description = "Fix isEmpty to consider whitespace"
)
public class StringUtilsPatch {
    public static boolean isEmpty(String str) {
        return str == null || str.trim().length() == 0;
    }
    public static String trim(String str) {
        return str == null ? null : str.trim();
    }
}

4. Packaging and Deployment

Compile Patch

# 1. Compile patch class (needs original project's classpath)
javac -cp "target/classes:target/lib/*" src/main/java/patches/UserServicePatch.java
# 2. Package into jar (includes annotation metadata)
jar cf UserService-1.0.1.jar -C target/classes patches/UserServicePatch.class
# 3. Copy jar to the patches directory
cp *.jar ./patches/

Start Application with Agent

java -javaagent:target/springboot-hot-patch-1.0.0-agent.jar \
    -Dhotpatch.enabled=true \
    -Dhotpatch.path=./patches \
    -jar target/springboot-hot-patch-1.0.0.jar

5. Application Configuration

# application.properties
spring.application.name=springboot-hot-patch
server.port=8080

# Hot Patch Configuration
hotpatch.enabled=true
hotpatch.path=./patches

6. Test Verification

@RestController
@RequestMapping("/api/test")
public class TestController {
    @Autowired
    private UserService userService;
    @GetMapping("/user")
    public String testUser(@RequestParam(value = "id", required = false) Long id) {
        try {
            int len = userService.getUserNameLength(id);
            return "用户名长度: " + len;
        } catch (Exception e) {
            return "错误: " + e.getMessage();
        }
    }
    @GetMapping("/string-utils")
    public boolean testStringUtils(@RequestParam(defaultValue = "   ") String str) {
        return StringUtils.isEmpty(str);
    }
    @GetMapping("/math/{a}/{b}")
    public String testMath(@PathVariable int a, @PathVariable int b) {
        try {
            int result = MathHelper.divide(a, b);
            return "计算结果: " + a + " / " + b + " = " + result;
        } catch (Exception e) {
            return "错误: " + e.getMessage();
        }
    }
}

Best Practices

Clear naming convention : patch class name = original class name + Patch.

Version management : use semantic version numbers.

Thorough testing : patches must pass strict tests before deployment.

Minimal changes : only fix the necessary issue, avoid adding new features.

Deployment Process

Development stage – develop and test patches locally.

Testing stage – verify patches in a pre‑production environment.

Review stage – conduct code review and security checks.

Deployment stage – hot‑load patches in production via the API.

Monitoring stage – observe patch impact and system stability.

Monitoring Alert Example

@EventListener
public void onPatchLoaded(PatchLoadedEvent event) {
    alertService.sendAlert(
        "Hot Patch Loaded",
        String.format("Patch %s:%s loaded successfully", event.getPatchName(), event.getVersion())
    );
    auditService.log("PATCH_LOADED", event.getPatchName(), SecurityContextHolder.getContext().getAuthentication().getName());
}

Applicable Scenarios

Urgent bug fix : quickly remediate production‑critical defects.

Performance optimization : apply temporary logic improvements.

Feature toggle : enable or disable functionality on the fly.

Parameter tuning : adjust algorithm parameters without redeploy.

Precautions

Use with caution : hot patches are emergency tools, not a substitute for a proper release process.

Thorough testing : ensure patches do not introduce new issues.

Permission control : enforce strict access rights to patch operations.

Backend DevelopmentJava Agenthot-patchRuntime Class Replacement
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.