How to Hot‑Deploy User‑Provided JAR Implementations with Spring and Reflection

This article walks through building a Java interface, providing two implementation styles (Spring‑managed and pure‑reflection), loading user‑uploaded JARs at runtime, dynamically registering or removing beans in the Spring container, and testing the whole hot‑deployment cycle with concrete code examples.

Java Web Project
Java Web Project
Java Web Project
How to Hot‑Deploy User‑Provided JAR Implementations with Spring and Reflection

Define a Simple Interface

We start with a minimal calculator interface that declares two methods, int calculate(int a, int b) and int add(int a, int b):

public interface Calculator {
    int calculate(int a, int b);
    int add(int a, int b);
}

Two Implementation Strategies

The author distinguishes two ways a user may implement the interface:

Annotation mode – the implementation is a Spring bean and will be injected by the container.

Reflection mode – the implementation is instantiated manually via reflection.

Both implementations are shown below.

@Service
public class CalculatorImpl implements Calculator {
    @Autowired
    CalculatorCore calculatorCore;

    // Annotation mode
    @Override
    public int calculate(int a, int b) {
        int c = calculatorCore.add(a, b);
        return c;
    }

    // Reflection mode
    @Override
    public int add(int a, int b) {
        return new CalculatorCore().add(a, b);
    }
}

The auxiliary CalculatorCore class contains the actual arithmetic logic:

@Service
public class CalculatorCore {
    public int add(int a, int b) {
        return a + b;
    }
}

Reflection‑Based Hot Deployment

When a user uploads a JAR, the system records the JAR's absolute path ( jarAddress) and builds a file:/ URL ( jarPath). The fully‑qualified class name of the implementation is supplied by the user. The following method loads the JAR with a URLClassLoader, obtains the Class object, creates an instance, and invokes add:

public static void hotDeployWithReflect() throws Exception {
    URLClassLoader urlClassLoader = new URLClassLoader(
        new URL[]{ new URL(jarPath) },
        Thread.currentThread().getContextClassLoader()
    );
    Class clazz = urlClassLoader.loadClass("com.nci.cetc15.calculator.impl.CalculatorImpl");
    Calculator calculator = (Calculator) clazz.newInstance();
    int result = calculator.add(1, 2);
    System.out.println(result);
}

Annotation‑Based Hot Deployment (Spring)

If the uploaded JAR contains Spring‑managed beans, the system must scan every class in the JAR, detect Spring annotations, and register the bean definitions into the current ApplicationContext. The loading of the JAR is identical to the reflection case; the difference lies in the post‑processing loop:

public static void hotDeployWithSpring() throws Exception {
    Set<String> classNameSet = DeployUtils.readJarFile(jarAddress);
    URLClassLoader urlClassLoader = new URLClassLoader(
        new URL[]{ new URL(jarPath) },
        Thread.currentThread().getContextClassLoader()
    );
    for (String className : classNameSet) {
        Class clazz = urlClassLoader.loadClass(className);
        if (DeployUtils.isSpringBeanClass(clazz)) {
            BeanDefinitionBuilder beanDefinitionBuilder =
                BeanDefinitionBuilder.genericBeanDefinition(clazz);
            defaultListableBeanFactory.registerBeanDefinition(
                DeployUtils.transformName(className),
                beanDefinitionBuilder.getBeanDefinition()
            );
        }
    }
}

The utility class DeployUtils provides three key methods: readJarFile(String jarAddress) – iterates over the JAR entries, collects fully‑qualified class names, and returns a Set<String> of them. isSpringBeanClass(Class<?> clazz) – returns true if the class is not an interface or abstract class and carries a Spring stereotype annotation ( @Component, @Repository, or @Service). transformName(String className) – converts the class name's simple name to a bean name by lower‑casing the first character (e.g., CalculatorImplcalculatorImpl).

public static Set<String> readJarFile(String jarAddress) throws IOException {
    Set<String> classNameSet = new HashSet<>();
    JarFile jarFile = new JarFile(jarAddress);
    Enumeration<JarEntry> entries = jarFile.entries();
    while (entries.hasMoreElements()) {
        JarEntry jarEntry = entries.nextElement();
        String name = jarEntry.getName();
        if (name.endsWith(".class")) {
            String className = name.replace(".class", "")
                                 .replaceAll("/", ".");
            classNameSet.add(className);
        }
    }
    return classNameSet;
}

public static boolean isSpringBeanClass(Class<?> cla) {
    if (cla == null || cla.isInterface() || Modifier.isAbstract(cla.getModifiers())) {
        return false;
    }
    if (cla.getAnnotation(Component.class) != null) return true;
    if (cla.getAnnotation(Repository.class) != null) return true;
    if (cla.getAnnotation(Service.class) != null) return true;
    return false;
}

public static String transformName(String className) {
    String tmp = className.substring(className.lastIndexOf(".") + 1);
    return tmp.substring(0,1).toLowerCase() + tmp.substring(1);
}

Removing Beans When a JAR Is Deleted

When a JAR is replaced or removed, the previously registered beans must be deregistered from the same Spring context. The removal mirrors the registration logic:

public static void delete() throws Exception {
    Set<String> classNameSet = DeployUtils.readJarFile(jarAddress);
    URLClassLoader urlClassLoader = new URLClassLoader(
        new URL[]{ new URL(jarPath) },
        Thread.currentThread().getContextClassLoader()
    );
    for (String className : classNameSet) {
        Class clazz = urlClassLoader.loadClass(className);
        if (DeployUtils.isSpringBeanClass(clazz)) {
            defaultListableBeanFactory.removeBeanDefinition(
                DeployUtils.transformName(className)
            );
        }
    }
}

Test Harness

A simple test program simulates the user uploading the JAR. It creates a Spring ApplicationContext, obtains the bean factory, and repeatedly attempts hot deployment until the JAR appears in the designated directory. If the JAR is missing, an exception is caught, printed, and the thread sleeps for ten seconds before retrying.

ApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("applicationContext.xml");
DefaultListableBeanFactory defaultListableBeanFactory =
    (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
while (true) {
    try {
        hotDeployWithReflect();
        // hotDeployWithSpring();
        // delete();
    } catch (Exception e) {
        e.printStackTrace();
        Thread.sleep(1000 * 10);
    }
}

Overall, the article demonstrates a complete workflow for dynamic, hot‑replaceable plugin development in a Java/Spring backend, covering interface definition, dual implementation paths, class‑loader manipulation, Spring bean registration/removal, and a practical test loop.

JavaReflectionSpringDynamic LoadingHot DeploymentJar
Java Web Project
Written by

Java Web Project

Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.

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.