ServiceLoader in Java
Plug-in discovery with provides/uses.
A job board for implementations
**ServiceLoader separates an API from its implementations. The consumer depends only on an interface and declares uses. Each provider declares provides ... with ...**. At runtime, ServiceLoader.load(Gateway.class) discovers them.
module com.shop.app { // consumer
uses com.pay.Gateway;
}
module com.stripe.pay { // provider
provides com.pay.Gateway
with com.stripe.StripeGateway;
}On the class path
Without modules, a provider registers itself with a text file: **META-INF/services/com.pay.Gateway**, containing the implementation's class name. Consumer code is the same either way.
// META-INF/services/com.pay.Gateway
com.stripe.StripeGateway
var loader = ServiceLoader.load(Gateway.class);
for (Gateway g : loader) g.connect();Nobody applied
What does this print?
interface Codec { String name(); }
void main() {
var all = ServiceLoader.load(Codec.class);
var first = all.findFirst();
System.out.println(all.stream().count());
System.out.println(first.isPresent());
}0 false1 trueThrows ServiceConfigurationError
Show the answer
ServiceLoader only finds registered providers. Nothing provides Codec here, so the stream is empty and findFirst() returns an empty Optional. No error, just nothing.
Provider rules
ServiceLoader must create providers knowing nothing else about them. So a provider needs either a public no-arg constructor or, in a named module, a **public static provider() method** that returns the instance.
Writing the class isn't enough
Implementing the interface doesn't register anything. Forget the provides line (or the META-INF/services file) and ServiceLoader silently finds nothing. Also mind the words: the provider says **provides, the consumer says uses**, and exports registers nothing.
module com.stripe.pay {
requires com.pay.api;
// missing: provides ... with ...
}Plug-ins in the JDK itself
Logging facades find their backend this way: SLF4J 2 discovers a binding like Logback through ServiceLoader. The app only knows the interface, so new providers work just by being added to the module path or class path.
Key takeaways
- Consumer: uses + ServiceLoader.load
- Provider: provides ... with ...
- Class path: META-INF/services/<interface name>
- Providers need a public no-arg constructor or provider() method
💡 ServiceLoader is a job board: the app posts the role (interface), and any provider JAR that applies gets hired at runtime.
Since JDBC 4.0 (Java 6), database drivers register through META-INF/services/java.sql.Driver, which is why modern code never needs Class.forName("com.mysql.Driver").
Practice questions
What does this print?
interface Plugin { String name(); }
void main() {
var found = ServiceLoader.load(Plugin.class)
.findFirst();
System.out.println(found.isPresent());
}- false
- Throws ServiceConfigurationError
- true
Check your answer
false. ServiceLoader only finds registered providers. Nothing provides Plugin here, so findFirst() returns an empty Optional.
New payment gateways should work just by dropping a JAR on the module path, without recompiling the app. Which design fits?
- A Gateway interface discovered with ServiceLoader (uses/provides)
- A static Map filled in main()
- Class.forName over a hard-coded list of names
- A switch statement over gateway names
Check your answer
A Gateway interface discovered with ServiceLoader (uses/provides). ServiceLoader is the JDK's built-in plug-in mechanism: the app only knows the interface, and providers register themselves.