What Is SPI?
SPI (Service Provider Interface) is a mechanism in Java that enables dynamic discovery of service implementations at runtime. It scans the META-INF/services/ directory on the classpath for files named after fully qualified interface names. Each file lists concrete implementation classes, allowing frameworks to load them without hardcoding dependencies.
This pattern promotes loose coupling—core APIs define contracts, while third parties provide implementations. A classic example is JDBC: the java.sql.Driver interface is implemented by vendors like MySQL or SQL Server, and the JDK uses SPI to automatically detect and load these drivers.
Implementing Custom Services with SPI
To demonstrate SPI usage, consider a modular project structure:
- DbInterface: Defines the
DBConnectionServiceinterface. - MySqlConnection: Implements the interface for MySQL.
- SqlServerConnection: Implements the interface for SQL Server.
- WebProject: Consumes available implementations via
ServiceLoader.
Step 1: Define the Service Interface
In the DbInterface module, declare a simple interface:
package com.aphysia.sql;
public interface DBConnectionService {
void connect();
}
Step 2: Implement for MySQL
The MySqlConnection module depends on DbInterface and provides an implementation:
package com.aphysia.mysql;
import com.aphysia.sql.DBConnectionService;
public class MysqlConnectionServiceImpl implements DBConnectionService {
public void connect() {
System.out.println("Connecting to MySQL...");
}
}
Create a provider configuration file at src/main/resources/META-INF/services/com.aphysia.sql.DBConnectionService with content:
com.aphysia.mysql.MysqlConnectionServiceImpl
Step 3: Implement for SQL Server
Similarly, the SqlServerConnection module includes:
package com.aphysia.sqlserver;
import com.aphysia.sql.DBConnectionService;
public class SqlServerConnectionServiceImpl implements DBConnectionService {
public void connect() {
System.out.println("Connecting to SQL Server...");
}
}
With its corresponding provider file META-INF/services/com.aphysia.sql.DBConnectionService:
com.aphysia.sqlserver.SqlServerConnectionServiceImpl
Step 4: Use Available Implementations
The WebProject depends on both implementation modules. At runtime, it loads all available services:
import com.aphysia.sql.DBConnectionService;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<DBConnectionService> loader = ServiceLoader.load(DBConnectionService.class);
for (DBConnectionService service : loader) {
service.connect();
}
}
}
If both JARs are on the claspsath, output is:
Connecting to MySQL...
Connecting to SQL Server...
Omitting one dependency excludes its implementation—no code changes needed.
How ServiceLoader Works Internally
ServiceLoader lazily discovers providers by:
- Locating all
META-INF/services/<interface-name>files viaClassLoader.getResources(). - Parsing each file line-by-line to extract implementation class names.
- Using reflection (
Class.forName()) to instantiate each class. - Caching instances in a
LinkedHashMapto preserve order and avoid re-instantiation.
The iterator returned by ServiceLoader combines cached instances with newly discovered ones during traversal.
Real-World SPI Applications
JDBC leverages SPI extensively. For instance, mysql-connector-java includes META-INF/services/java.sql.Driver containing:
com.mysql.cj.jdbc.Driver
This eliminates the need for explicit Class.forName("com.mysql.cj.jdbc.Driver") calls in modern applications.
Other notable uses include SLF4J (for binding logging implementations) and Java’s own java.nio.file.spi.FileSystemProvider. SPI enables extensible, pluggable architectures where consumers remain agnostic to specific providers.