Java SPI separates a service contract from one or more providers and
from the consumer that selects them. The consumer depends on the service
type and uses ServiceLoader, so providers can be added at deployment without
an application dependency on concrete classes.
Classpath Services #
Before modules, a provider JAR listed implementations in
META-INF/services/<fully.qualified.ServiceName>. The UTF-8 file has one
provider class name per line; blank lines and # comments are allowed. Each
provider must be accessible to the loader used for discovery and offer a public
no-argument constructor.
ServiceLoader<Codec> codecs = ServiceLoader.load(Codec.class);
for (Codec codec : codecs) {
if (codec.supports(mediaType)) return codec;
}Discovery and provider instantiation are lazy. Iterating, streaming providers,
or calling Provider.get() can load and initialize a provider, so malformed
configuration, missing dependencies, inaccessible types, or failed
constructors surface as ServiceConfigurationError. Treat provider code as
deployment-supplied code, select an explicit class loader in containers or
plugin systems, and fail with diagnostics at a controlled startup boundary.
Module Services #
With JPMS, the consumer module declares uses com.example.Codec; a provider
declares provides com.example.Codec with com.example.JsonCodec. Module
resolution accounts for service providers as defined by the module-system
rules. A provider class need not be exported merely for ServiceLoader; a
named-module provider can have a public no-argument constructor or a public
static provider method, subject to the service declaration constraints.
ServiceLoader.load(service, loader) follows the supplied loader’s visibility,
whereas the module-aware overloads operate from a module layer. The loader and
layer are part of the service boundary, not incidental details.
Classpath configuration remains supported for unnamed and automatic modules, which enables migration. Avoid publishing both mechanisms without testing the resolved provider set, because duplicates and ordering assumptions make selection non-portable.
JDBC as an Evolution Example #
Early JDBC driver usage often called Class.forName("vendor.Driver") to trigger
a driver’s static registration with DriverManager. JDBC 4 introduced
service-provider loading for java.sql.Driver, allowing compatible driver
JARs to be discovered from META-INF/services/java.sql.Driver. Modern
applications should still configure the intended driver and connection
properties explicitly; automatic discovery removes a bootstrap call, not
database authentication or version compatibility concerns.
Comments
Read or comment on GitHub (sign-in required to comment)