Why Spring Chooses Its Own SPI Over Java’s Native ServiceLoader
The article compares Java’s native SPI with Spring’s custom SPI, explains the fundamental flaws of ServiceLoader, details SpringFactoriesLoader’s lazy loading and conditional filtering, and shows why SpringBoot auto‑configuration relies entirely on its own SPI mechanism.
SPI Basics
SPI (Service Provider Interface) is a design pattern that externalizes implementation classes via configuration files, enabling plug‑in extensions without hard‑coding dependencies.
Java Native SPI Mechanism
The standard process involves defining a top‑level interface, providing implementation classes, creating a META-INF/services/<interface‑full‑name> file that lists the implementations, and loading them with ServiceLoader.load(Interface.class). This loader reads the file, parses all class names, and instantiates every implementation eagerly.
public interface DataSourceProvider {
String getType();
}
public class MysqlProvider implements DataSourceProvider {
@Override
public String getType() { return "mysql"; }
}
// META-INF/services/com.demo.spi.DataSourceProvider
com.demo.spi.impl.MysqlProviderSix Defects of Java SPI
All implementations are instantiated immediately, causing large memory and startup overhead.
No conditional filtering; cannot select implementations based on classpath, configuration, or bean presence.
Only interface implementations can be loaded; configuration classes annotated with @Configuration are unsupported.
Each file can declare only one interface, preventing unified management of different extension types.
No ordering, exclusion, or overriding capabilities, making precise load control impossible.
Instances are plain Java objects, outside the Spring IoC container, so they cannot use @Autowired, AOP, transactions, or lifecycle callbacks.
Spring’s Custom SPI (SpringFactoriesLoader)
Spring implements its own loader that caches class‑name strings without creating objects. Actual instantiation occurs only when the container needs the bean, providing true lazy loading.
First Generation: spring.factories
This file resides at META-INF/spring.factories and uses a properties format where the key is an extension interface or annotation and the value is a comma‑separated list of implementation class names. It supports grouping of different extension types in a single file.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration
org.springframework.context.ApplicationListener=\
com.xxx.listener.StartListenerSecond Generation: AutoConfiguration.imports
Introduced in SpringBoot 2.7+, this plain‑text file (
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) lists one fully‑qualified auto‑configuration class per line, eliminating properties parsing and improving startup speed. It works together with @AutoConfiguration to provide built‑in ordering.
com.demo.autoconfig.RedisAutoConfigCore Advantages of Spring SPI
Lazy loading – class names are cached; objects are created only when required.
Conditional filtering – works with @Conditional annotations to load beans based on classpath, configuration, or bean presence.
Supports loading of configuration classes, listeners, initializers, and post‑processors, not just interface implementations.
Group management – extensions are keyed (e.g., EnableAutoConfiguration, ApplicationListener), allowing clear organization.
Ordering and exclusion – annotations like @AutoConfigureOrder, @AutoConfigureBefore, and @SpringBootApplication(exclude=…) control load order and enable manual exclusion.
Full integration with the Spring IoC container – beans participate in dependency injection, AOP, transactions, and lifecycle callbacks.
Multi‑jar merging – all spring.factories or imports files on the classpath are merged, preventing conflicts among many starters.
Side‑by‑Side Comparison
Driver class: ServiceLoader vs SpringFactoriesLoader.
Configuration path: META-INF/services/<interface> vs META-INF/spring.factories (old) / META-INF/spring/*.imports (new).
File format: single‑line class name per interface vs properties key‑value pairs or plain text supporting groups.
Instantiation: eager full instantiation vs lazy class‑name caching.
Conditional filtering: none vs full @Conditional support.
Supported types: only interface implementations vs interfaces, @Configuration classes, listeners, initializers, post‑processors.
Grouping: none vs key‑based grouping.
Ordering/exclusion: not supported vs supported via ordering annotations and exclude attribute.
IoC integration: isolated objects vs managed beans with @Autowired, AOP, transactions.
Performance: high memory usage due to eager loading vs reduced memory footprint.
Typical use‑case: simple JDBC drivers vs SpringBoot auto‑configuration, custom starters, framework extensions.
Why Spring Does Not Use Native SPI for Auto‑Configuration
Native SPI forces full instantiation, which makes startup unbearably slow when hundreds of auto‑configuration classes exist.
It lacks conditional filtering, so Spring cannot load starters only when required or let user‑defined beans override defaults.
It only supports interface implementations, preventing loading of @Configuration classes that define beans.
Instances created by ServiceLoader are outside the Spring container, so they cannot benefit from dependency injection, AOP, or transaction management.
Spring still uses Java SPI for low‑level utilities such as JDBC driver loading, but all SpringBoot auto‑configuration and extensible framework points are built on Spring’s own SPI.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
