Backend Development 13 min read

Master Java SPI: From Service Provider Interface to Spring Boot Auto‑Configuration

This article explains Java’s Service Provider Interface (SPI) mechanism, compares it with traditional APIs, demonstrates how to define interfaces, implement services, and discover them using ServiceLoader, and shows real‑world applications such as Spring Boot auto‑configuration and logging frameworks like SLF4J.

macrozheng
macrozheng
macrozheng
Master Java SPI: From Service Provider Interface to Spring Boot Auto‑Configuration

Many interviewers ask about Spring Boot's auto‑configuration and expect the answer to mention Java's SPI (Service Provider Interface) mechanism, the

spring.factories

file, and the

EnableAutoConfiguration

annotation.

1. Introduction

SPI stands for Service Provider Interface, which is a service discovery mechanism. In Java, it allows a caller to define an interface that multiple providers can implement, and the runtime can discover those implementations.

SPI concept diagram
SPI concept diagram

Compared with a regular API, SPI emphasizes the contract that the provider must satisfy, and the caller can discover providers without knowing their concrete classes.

2. Defining the Interface

Using a smart‑home air‑conditioner example, we first define a common interface that all air‑conditioner models must implement.

<code>public interface IAircondition {
    // Get model type
    String getType();

    // Turn on/off
    void turnOnOff();

    // Adjust temperature
    void adjustTemperature(int temperature);

    // Change mode
    void changeModel(int modelId);
}
</code>

We package this interface into a JAR named

aircondition-standard

and install it with Maven:

<code>mvn clean install</code>

3. Service Implementation

Each air‑conditioner type provides its own implementation and declares the implementation class in

META-INF/services/com.cn.hydra.IAircondition

.

Hanging‑type implementation:

<code>public class HangingTypeAircondition implements IAircondition {
    public String getType() { return "HangingType"; }
    public void turnOnOff() { System.out.println("挂式空调开关"); }
    public void adjustTemperature(int i) { System.out.println("挂式空调调节温度"); }
    public void changeModel(int i) { System.out.println("挂式空调更换模式"); }
}
</code>

Vertical‑type implementation (similar structure):

<code>public class VerticalTypeAircondition implements IAircondition {
    public String getType() { return "VerticalType"; }
    public void turnOnOff() { System.out.println("立式空调开关"); }
    public void adjustTemperature(int i) { System.out.println("立式空调调节温度"); }
    public void changeModel(int i) { System.out.println("立式空调更换模式"); }
}
</code>
Project structure diagram
Project structure diagram

4. Service Discovery

In the consumer application we add the two JARs as dependencies and use

ServiceLoader

to load all

IAircondition

implementations.

<code>public class AirconditionApp {
    public static void main(String[] args) {
        new AirconditionApp().turnOn("VerticalType");
    }
    public void turnOn(String type) {
        ServiceLoader&lt;IAircondition&gt; loader = ServiceLoader.load(IAircondition.class);
        for (IAircondition ac : loader) {
            System.out.println("检测到:" + ac.getClass().getSimpleName());
            if (type.equals(ac.getType())) {
                ac.turnOnOff();
            }
        }
    }
}
</code>

The output shows that both implementations are discovered and the correct one is invoked based on the type parameter.

Runtime discovery result
Runtime discovery result

5. Underlying Principle

The core of SPI is the

ServiceLoader

class. It lazily loads provider names from

META-INF/services

, caches them, and creates instances via reflection when

iterator()

is traversed. The iterator first checks an internal cache (

providers

) and then falls back to a

LazyIterator

that reads the configuration files.

ServiceLoader source snippet
ServiceLoader source snippet

6. Real‑World Applications

SPI is widely used in logging frameworks. For example, SLF4J is a façade that relies on SPI to bind to a concrete logger such as Log4j2 or Reload4j. The binding JAR contains a provider class listed in

META-INF/services/org.slf4j.spi.SLF4JServiceProvider

, which SLF4J loads at runtime.

<code>&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
    &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
    &lt;version&gt;2.0.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
    &lt;artifactId&gt;slf4j-reload4j&lt;/artifactId&gt;
    &lt;version&gt;2.0.3&lt;/version&gt;
&lt;/dependency&gt;
</code>

The binding JAR’s

META-INF/services

directory registers

Reload4jServiceProvider

, which implements

SLF4JServiceProvider

and supplies the actual

LoggerFactory

and

Logger

instances.

7. Summary

Java’s SPI offers a flexible service‑discovery mechanism that decouples callers from providers, making it easy to extend frameworks such as Spring Boot and SLF4J. Its drawback is that loading an interface may instantiate all providers, potentially pulling in unnecessary classes, but overall it provides a powerful pattern for modular architecture.

JavaSpring Bootdependency injectionSPISlf4jServiceLoaderAuto‑Configuration
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.