Backend Development 13 min read

Understanding Java SPI and Building a Modular Plugin System with Spring Boot

This article explains Java's Service Provider Interface (SPI), compares it with traditional APIs, and provides a step‑by‑step guide with Maven project setup, code examples, custom class loader, and Spring Boot integration to build a modular plugin system.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Java SPI and Building a Modular Plugin System with Spring Boot

Java's Service Provider Interface (SPI) is a mechanism that allows developers to define multiple implementations for an interface and have them discovered and loaded dynamically at runtime. Implementations are placed in the META-INF/services directory and referenced by a configuration file containing the fully qualified class name.

The article contrasts SPI with traditional APIs: APIs are written by developers and called directly, while SPI interfaces are defined by frameworks and implemented by third‑party providers, loaded via configuration files, offering greater flexibility and runtime replaceability.

Implementation Process

1. Project structure is created as a multi‑module Maven project ( sa-auth ) with modules for the bus, the SPI definition ( sa-auth-plugin ), and a sample LDAP implementation ( sa-auth-plugin-ldap ).

2. The parent pom.xml for sa-auth is defined, including Spring Boot parent, Lombok dependency, and module listings.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.16.RELEASE</version>
    </parent>
    ...
</project>

3. The SPI interface AuthPluginService is defined in sa-auth-plugin :

package com.vijay.csauthplugin.service;

/**
 * Plugin SPI interface
 */
public interface AuthPluginService {
    /** Login authentication */
    boolean login(String userName, String password);
    /** Name used to locate the service */
    String getAuthServiceName();
}

4. An LDAP implementation ( LdapProviderImpl ) is created in sa-auth-plugin-ldap and packaged as a JAR. The implementation is registered in META-INF/services/com.vijay.csauthplugin.service.AuthPluginService with the class name com.vijay.csauthplugin.ldap.LdapProviderImpl .

5. A default provider ( DefaultProviderImpl ) is added to the bus module, also registered via META-INF/services .

6. A custom class loader ( PluginClassLoader ) and an ExternalJarLoader utility load JAR files from an external directory at application startup.

public class ExternalJarLoader {
    public static void loadExternalJars(String externalDirPath) {
        File dir = new File(externalDirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new IllegalArgumentException("Invalid directory path");
        }
        List
urls = new ArrayList<>();
        for (File file : Objects.requireNonNull(dir.listFiles())) {
            if (file.getName().endsWith(".jar")) {
                urls.add(file.toURI().toURL());
            }
        }
        PluginClassLoader customClassLoader = new PluginClassLoader(urls.toArray(new URL[0]));
        Thread.currentThread().setContextClassLoader(customClassLoader);
    }
}

7. The Spring Boot application ( CsAuthBusApplication ) invokes the loader and runs the service. A PluginProvider uses ServiceLoader to obtain the first non‑default implementation, falling back to DefaultProviderImpl when none is found.

public class PluginProvider {
    public static AuthPluginService getAuthPluginService() {
        ServiceLoader
loader = ServiceLoader.load(AuthPluginService.class);
        for (AuthPluginService service : loader) {
            if (!(service instanceof DefaultProviderImpl)) {
                return service;
            }
        }
        return loader.iterator().next(); // default
    }
}

8. A Spring configuration bean exposes the selected AuthPluginService , and a REST controller demonstrates usage by returning the service name and a login result.

@RestController
public class TestController {
    @Resource
    private AuthPluginService authPluginService;

    @GetMapping("test")
    public Object test() {
        Map
map = new HashMap<>();
        map.put("name", authPluginService.getAuthServiceName());
        map.put("login", authPluginService.login("vijay", "123456"));
        return map;
    }
}

Running the application first returns the default implementation. After placing the LDAP JAR in the external directory and restarting, the request returns the LDAP provider, demonstrating dynamic plugin loading via Java SPI.

JavapluginmavenSpringBootSPIServiceLoader
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.