Why Manual JAR Deployment Is Outdated: Embrace Dynamic Hot‑Deployment Plugins

The article explains why rebuilding and redeploying a whole JAR for small rule or script changes is inefficient, distinguishes development‑time hot reload from production hot deployment, and shows how a plugin architecture using URLClassLoader and ServiceLoader provides granular, rollback‑friendly, and extensible updates while warning of classloader leaks and dependency conflicts.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Why Manual JAR Deployment Is Outdated: Embrace Dynamic Hot‑Deployment Plugins

Manual JAR replacement is inefficient

Rebuilding, uploading, stopping the service, swapping the JAR, and restarting for a tiny online change—such as a rule engine tweak, activity script, risk strategy, or report calculation—adds unnecessary overhead because the core system remains untouched.

Do not confuse development‑time hot reload with production hot deployment

Spring Boot DevTools, which relies on a dual‑classloader scheme (stable third‑party libraries in a base loader and mutable business classes in a restart loader), is designed for developer productivity, not for production use. When the application is started with java -jar, DevTools treats the environment as production, disables hot reload, and introduces security risks.

Plugin‑based loading is the practical solution

The recommended approach is to isolate the frequently changing part into a separate plugin JAR. The host application exposes stable extension interfaces (e.g., Plugin, Rule). External JARs implement these interfaces; at runtime the host scans a directory, loads the JAR, instantiates the implementation classes, and registers them in the container, leaving the core system untouched.

Underlying Java mechanisms

Java provides URLClassLoader and ServiceLoader for this purpose. Oracle’s documentation explains that external JARs can be added to the search path at runtime and implementations can be loaded via an interface. In a Spring context this can be further refined by registering the loaded classes as beans or by creating a child container for each plugin.

A minimal viable structure

The system can be split into three layers:

Host application: handles upload, authentication, plugin lifecycle, and runtime control.

Common interface package: contains only stable interfaces such as Plugin and Rule.

Business plugin JARs: implement the interfaces and can be iterated independently.

The host only depends on the interface definitions, not on concrete implementations.

public interface RulePlugin {
    String code();

    Object execute(Map<String, Object> context);
}

Loading logic uses a dedicated class loader per plugin and ServiceLoader to discover implementations. The plugin JAR must contain a META-INF/services descriptor; otherwise the loader cannot find the implementation.

Path jarPath = Paths.get("plugins/risk-rule.jar");
URL[] urls = { jarPath.toUri().toURL() };
try (URLClassLoader classLoader = new URLClassLoader(urls, RulePlugin.class.getClassLoader())) {
    ServiceLoader<RulePlugin> loader = ServiceLoader.load(RulePlugin.class, classLoader);
    for (RulePlugin plugin : loader) {
        System.out.println("loaded plugin: " + plugin.code());
    }
}

Why this beats manual JAR swaps

Fine‑grained releases : updating a single rule plugin does not require touching the host or restarting the whole service.

Easy rollback : a faulty plugin can be replaced with the previous version without a full package rollback.

High extensibility : many independent plugins can coexist under one host, allowing different teams to work autonomously.

Operational friendliness : the upload/enable/disable/version‑switch/gray‑release workflow can be built into an admin UI, turning the mechanism into a business‑efficiency tool.

Common pitfalls

Unloading is the biggest risk: if the old class loader is still referenced by threads, static fields, caches, scheduled tasks, or Spring singletons, it cannot be garbage‑collected, causing Metaspace growth and eventual JVM failure.

Dependency conflicts also arise when a plugin bundles a different version of a library than the host. You must decide on a parent‑first strategy (share host dependencies) or full isolation (separate class loader or even a separate Spring context) and avoid mixed approaches.

Practical recommendation

Use DevTools or IDE hot‑swap only for local development speed. For production environments that require frequent changes to rules, scripts, strategies, or workflow nodes, adopt a plugin‑based dynamic loading approach. Separate high‑frequency change modules into plugins while keeping the stable core unchanged to achieve reliable hot deployment.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaPlugin Architecturespring-bootServiceLoaderHot DeploymentURLClassLoader
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.