Backend Development 7 min read

Implementing SPI-like Extensions in Spring Boot

Spring Boot can emulate SPI plug‑in extensions by using native Java ServiceLoader, conditional bean registration with @ConditionalOnClass/@ConditionalOnProperty, custom FactoryBean implementations, or programmatic BeanDefinitionRegistryPostProcessor registration, each enabling dynamic service loading based on configuration or runtime conditions.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Implementing SPI-like Extensions in Spring Boot

SPI (Service Provider Interface) allows plug‑in extensions. Spring Boot does not implement SPI directly, but its DI and extension mechanisms can achieve similar effects.

Four approaches are presented:

1. Native Java SPI – place provider configuration files under META-INF/services and load with java.util.ServiceLoader . Example interfaces and implementations are shown.

package com.example.spi;
public interface LoggingService {
    void log(String message);
}
public class ConsoleLoggingServiceImpl implements LoggingService {
    @Override
    public void log(String message) {
        System.out.println("Console: " + message);
    }
}
public class FileLoggingServiceImpl implements LoggingService {
    @Override
    public void log(String message) {
        System.out.println("File: " + message);
    }
}

Configuration file META-INF/services/com.example.spi.LoggingService lists the implementation class names.

com.example.spi.impl.ConsoleLoggingServiceImpl
com.example.spi.impl.FileLoggingServiceImpl

Loading example:

ServiceLoader
loader = ServiceLoader.load(LoggingService.class);
for (LoggingService s : loader) {
    s.log("Hello SPI!");
}

2. Spring conditional configuration – use @ConditionalOnClass and @ConditionalOnProperty to register beans only when certain conditions are met.

@Configuration
public class MyServiceConfig {
    @Bean
    @ConditionalOnClass(MyServiceImpl1.class)
    @ConditionalOnProperty(name = "service.impl", havingValue = "impl1")
    public MyService myService1() { return new MyServiceImpl1(); }

    @Bean
    @ConditionalOnClass(MyServiceImpl2.class)
    @ConditionalOnProperty(name = "service.impl", havingValue = "impl2")
    public MyService myService2() { return new MyServiceImpl2(); }
}

3. FactoryBean – implement FactoryBean to create the desired service instance based on a flag.

public class MyServiceFactoryBean implements FactoryBean
{
    private String typeFlag;
    public void setImplClass(String typeFlag) { this.typeFlag = typeFlag; }
    @Override
    public MyService getObject() throws Exception {
        if ("impl1".equals(typeFlag)) return new MyServiceImpl1();
        if ("impl2".equals(typeFlag)) return new MyServiceImpl2();
        throw new IllegalArgumentException("Invalid implementation class");
    }
    @Override public Class
getObjectType() { return MyService.class; }
    @Override public boolean isSingleton() { return true; }
}

Registering the FactoryBean:

@Configuration
public class MyServiceConfig {
    @Bean
    public MyServiceFactoryBean myService() {
        MyServiceFactoryBean fb = new MyServiceFactoryBean();
        fb.setImplClass("impl1");
        return fb;
    }
}

4. Custom BeanDefinition – implement BeanDefinitionRegistryPostProcessor to programmatically register a BeanDefinition.

public class MyServiceBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (someCondition()) {
            GenericBeanDefinition bd = new GenericBeanDefinition();
            bd.setBeanClassName("com.example.MyServiceImpl1");
            bd.setScope(BeanDefinition.SCOPE_SINGLETON);
            registry.registerBeanDefinition("myService", bd);
        }
    }
    // other methods omitted for brevity
}

Import the processor in the Spring Boot application:

@SpringBootApplication
@Import(MyServiceBeanDefinitionRegistryPostProcessor.class)
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class, args);
    }
}

These techniques enable dynamic, plug‑in style service loading within a Spring Boot project.

JavaSpring Bootdependency injectionFactoryBeanSPI
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

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.