Implementing Plugin Architecture in Spring Boot Applications

Java Plugin Implementation Approaches

ServiceLoader Mechanism

Java's ServiceLoader provides a standard SPI implementation. Define an interface with multiple implementations, then load them dynamically:

public interface NotificationService {
    void sendAlert(String message);
}

public class EmailNotifier implements NotificationService {
    @Override
    public void sendAlert(String msg) {
        System.out.println("Email notification: " + msg);
    }
}

public class SmsNotifier implements NotificationService {
    @Override
    public void sendAlert(String msg) {
        System.out.println("SMS notification: " + msg);
    }
}

Create a service file at META-INF/services/com.example.NotificationService containning:

com.example.EmailNotifier
com.example.SmsNotifier

Load implementations:

ServiceLoader<NotificationService> services = 
    ServiceLoader.load(NotificationService.class);
services.forEach(service -> service.sendAlert("Test"));

Configuration-Based Plugin Loading

Define plugins in application properties:

plugins:
  notification:
    implementations:
      - com.example.EmailNotifier
      - com.example.SmsNotifier

Load dynamically:

@ConfigurationProperties("plugins.notification")
public class PluginConfig {
    private List<String> implementations;
    
    public List<NotificationService> loadServices() throws Exception {
        List<NotificationService> services = new ArrayList<>();
        for (String className : implementations) {
            Class<?> clazz = Class.forName(className);
            services.add((NotificationService) clazz.getDeclaredConstructor().newInstance());
        }
        return services;
    }
}

Spring Boot Plugin Implemantation

Spring Factories Mechanism

Create META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.EmailAutoConfiguration,\
  com.example.SmsAutoConfiguration

Auto-configuration classes:

@Configuration
public class EmailAutoConfiguration {
    @Bean
    public NotificationService emailNotifier() {
        return new EmailNotifier();
    }
}

Practical Implementation Example

  1. Define core interface:
public interface PaymentProcessor {
    PaymentResult process(PaymentRequest request);
}
  1. Create implementations:
public class StripeProcessor implements PaymentProcessor {
    @Override
    public PaymentResult process(PaymentRequest request) {
        // Stripe implementation
    }
}
  1. Load processors dynamically:
@Service
public class PaymentService {
    private final Map<String, PaymentProcessor> processors;
    
    public PaymentService(List<PaymentProcessor> processors) {
        this.processors = processors.stream()
            .collect(Collectors.toMap(
                p -> p.getClass().getSimpleName(),
                Function.identity()));
    }
    
    public PaymentResult process(String processorName, PaymentRequest request) {
        PaymentProcessor processor = processors.get(processorName);
        if (processor == null) {
            throw new IllegalArgumentException("Unknown processor");
        }
        return processor.process(request);
    }
}
  1. External JAR loading:
public class PluginLoader {
    public void loadExternalPlugins(Path pluginDir) throws Exception {
        Files.list(pluginDir)
            .filter(path -> path.toString().endsWith(".jar"))
            .forEach(this::loadJar);
    }
    
    private void loadJar(Path jarPath) {
        try (URLClassLoader loader = new URLClassLoader(
            new URL[]{jarPath.toUri().toURL()},
            getClass().getClassLoader())) {
            
            ServiceLoader<PaymentProcessor> services = 
                ServiceLoader.load(PaymentProcessor.class, loader);
            services.stream().forEach(provider -> {
                // Register discovered plugins
            });
        }
    }
}

Tags: Spring Boot java Plugin Architecture SPI Modular Design

Posted on Sun, 10 May 2026 19:39:54 +0000 by dt_gry