Java Module 17 – Modules, packaging and distribution
ServiceLoader: services, providers and plug-ins
Define a Java service, offer providers with provides or META-INF/services, find them lazily with ServiceLoader, and build plug-ins that fail safely.
What you will learn
- Define a service interface and providers with provides and uses
- Load providers at run time with ServiceLoader
- Design a plug-in architecture without reflection hacks
Before you start
What you’ll learn · Before you start
What you will learn
- Define a service interface and providers with provides and uses
- Load providers at run time with ServiceLoader
- Design a plug-in architecture without reflection hacks
Before you start
On this page
A service in Java is an interface, or an abstract class, that a program needs, and a service provider is an
implementation of it that the program never names in its code. java.util.ServiceLoader finds the providers at run
time. A module that looks providers up declares uses com.example.Service;, and each plug-in module declares
provides com.example.Service with com.example.impl.Impl;. On the class path, a plug-in JAR lists its provider
classes in a text file instead, META-INF/services/com.example.Service. ServiceLoader creates providers lazily, only
when you ask for them, so a program can look at what is available first. This is how a host gains features without
changing its own code: put another plug-in on the module path, and the next run finds it. The JDK works this way too;
DriverManager, for one, finds JDBC drivers as providers of java.sql.Driver.
This lesson builds a shop checkout that finds its payment methods as plug-ins, on Eclipse Temurin 25.0.4.1.
A service and two providers
Four modules take part. kirana.pay exports the service, the interface PaymentMethod. Two plug-ins,
kirana.upi and kirana.card, each provide it. kirana.checkout uses it, and requires only kirana.pay:
Who declares what, and where ServiceLoader comes in
Text description of the diagram
The diagram shows four modules and the loader, from top to bottom.
- kirana.checkout declares uses PaymentMethod. When it runs, it calls ServiceLoader.load(PaymentMethod.class).
- ServiceLoader finds every module that declares provides PaymentMethod. A dashed arrow leads from it to a box of plug-ins found at run time, because the connection is made when the program runs, not written in the checkout's code.
- The box holds the two plug-ins: kirana.upi, which provides PaymentMethod with com.example.kirana.upi.Upi, and kirana.card, which provides it with com.example.kirana.card.Card.
- An arrow from the plug-ins leads to kirana.pay, which exports the interface PaymentMethod; both plug-ins require it, and so does the checkout. The checkout never requires a plug-in module, so plug-ins can be added or removed without changing it.
pay/src/com/example/kirana/pay/PaymentMethod.java
package com.example.kirana.pay;
/** A way to pay a bill. Plug-ins implement it, and the checkout finds them at run time. */
public interface PaymentMethod {
/** A short name the checkout shows, such as "UPI". */
String name();
/** What the customer is told to do for an amount in paise. */
String instructions(long paise);
/** Formats paise as rupees, such as 1418.50; enough for these plug-ins' messages. */
static String rupees(long paise) {
return paise / 100 + "." + String.format("%02d", paise % 100);
}
} upi/src/module-info.java · upi/src/module-info.java
/** A payment plug-in. It provides the service; nothing needs to know its class name. */
module kirana.upi {
requires kirana.pay;
provides com.example.kirana.pay.PaymentMethod with com.example.kirana.upi.Upi;
} upi/src/com/example/kirana/upi/Upi.java
package com.example.kirana.upi;
import com.example.kirana.pay.Named;
import com.example.kirana.pay.PaymentMethod;
@Named("UPI")
public class Upi implements PaymentMethod {
public Upi() {
System.out.println(" (the UPI plug-in is created)");
}
@Override
public String name() {
return "UPI";
}
@Override
public String instructions(long paise) {
return "scan the shop's QR code and approve ₹" + PaymentMethod.rupees(paise) + " in your UPI app";
}
} checkout/src/module-info.java · checkout/src/module-info.java
/** The shop's checkout: it uses the payment service and names no plug-in. */
module kirana.checkout {
requires kirana.pay;
uses com.example.kirana.pay.PaymentMethod;
} checkout/src/com/example/kirana/checkout/Checkout.java
package com.example.kirana.checkout;
import com.example.kirana.pay.PaymentMethod;
import java.util.Comparator;
import java.util.List;
import java.util.ServiceLoader;
/** Lists every way to pay that a plug-in on the module path offers. */
public class Checkout {
public static void main(String[] args) {
long paise = 1_418_50;
System.out.println("Looking for plug-ins");
List<PaymentMethod> methods = ServiceLoader.load(PaymentMethod.class).stream()
.sorted(Comparator.comparing(provider -> provider.type().getName())) // the order found is not defined
.map(ServiceLoader.Provider::get)
.toList();
System.out.println("Bill of ₹" + PaymentMethod.rupees(paise) + ", " + methods.size()
+ (methods.size() == 1 ? " way" : " ways") + " to pay:");
for (PaymentMethod method : methods) {
System.out.println(" " + method.name() + ": " + method.instructions(paise));
}
}
} Each part of the arrangement has one job. The service module exports nothing but the interface and what it needs. A
plug-in requires the service module, declares provides … with … and exports nothing, as the ServiceLoader
documentation recommends, so no code can depend on its classes. The checkout declares uses and calls
ServiceLoader.load(PaymentMethod.class); it never requires a plug-in module, so plug-ins can come and go.
The interface lives in a module of its own for a reason. The checkout and every plug-in depend on it, and on nothing of each other, so the dependencies all point one way: a plug-in can be written, compiled and shipped by another team with only the service module in hand, and the checkout can be released without knowing which plug-ins exist.
Loading the providers at run time
from session import run
# The service, two plug-ins and the checkout, each compiled on its own and packed as a modular JAR; the checkout runs
# with both plug-ins on the module path, then with one of them, unchanged.
run(
"""
javac -d out/kirana.pay $(find pay/src -name "*.java")
javac -d out/kirana.upi --module-path out $(find upi/src -name "*.java")
javac -d out/kirana.card --module-path out $(find card/src -name "*.java")
javac -d out/kirana.checkout --module-path out $(find checkout/src -name "*.java")
for m in pay upi card checkout; do jar --create --file mods/kirana.$m.jar -C out/kirana.$m . || exit 1; done
jar --describe-module --file mods/kirana.upi.jar
java --module-path mods --module kirana.checkout/com.example.kirana.checkout.Checkout
java --module-path mods/kirana.pay.jar:mods/kirana.upi.jar:mods/kirana.checkout.jar --module kirana.checkout/com.example.kirana.checkout.Checkout
"""
) Output
# openjdk version "25.0.4.1" LTS $ javac -d out/kirana.pay $(find pay/src -name "*.java") $ javac -d out/kirana.upi --module-path out $(find upi/src -name "*.java") $ javac -d out/kirana.card --module-path out $(find card/src -name "*.java") $ javac -d out/kirana.checkout --module-path out $(find checkout/src -name "*.java") $ for m in pay upi card checkout; do jar --create --file mods/kirana.$m.jar -C out/kirana.$m . || exit 1; done $ jar --describe-module --file mods/kirana.upi.jar kirana.upi jar:mods/kirana.upi.jar!/module-info.class requires java.base mandated requires kirana.pay provides com.example.kirana.pay.PaymentMethod with com.example.kirana.upi.Upi contains com.example.kirana.upi $ java --module-path mods --module kirana.checkout/com.example.kirana.checkout.Checkout Looking for plug-ins (the Card plug-in is created) (the UPI plug-in is created) Bill of ₹1418.50, 2 ways to pay: Card: tap or insert the card; the terminal charges ₹1418.50 UPI: scan the shop's QR code and approve ₹1418.50 in your UPI app $ java --module-path mods/kirana.pay.jar:mods/kirana.upi.jar:mods/kirana.checkout.jar --module kirana.checkout/com.example.kirana.checkout.Checkout Looking for plug-ins (the UPI plug-in is created) Bill of ₹1418.50, 1 way to pay: UPI: scan the shop's QR code and approve ₹1418.50 in your UPI app
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 plugins_session.py
- The descriptor records the provider.
jar --describe-moduleshowsprovides com.example.kirana.pay.PaymentMethod with com.example.kirana.upi.Upiin the plug-in’s JAR, which is where ServiceLoader reads it. - Providers are created on request. Each plug-in prints a line from its constructor, and the lines appear only
after “Looking for plug-ins”, when
Provider.get()runs. - The order is chosen, not found. The ServiceLoader documentation says that the order of modules in a layer is not defined, so the checkout sorts the providers by class name before it creates them, and every run lists them the same way.
- Removing a plug-in is a deployment change. The second run left the card plug-in’s JAR off the module path: one way to pay, with nothing recompiled and nothing edited.
The same ServiceLoader.load call also finds plug-ins that are plain JARs on the class path, through their provider
files under META-INF/services. A host therefore keeps working when some of its plug-ins are modules and others are
not yet, which helps while a plug-in system moves to modules one JAR at a time.
The ServiceLoader API in one table
| Call or file | What it does |
|---|---|
ServiceLoader.load(S.class) |
A loader for the service S, which looks on the module path and the class path |
for (S s : loader) |
Creates each provider when the loop reaches it |
loader.stream() |
Gives Provider objects, which can be inspected before any provider is created |
Provider.type() |
The provider’s class, or the declared return type of its provider() method |
Provider.get() |
Creates the provider, or asks its provider() method for it |
loader.findFirst() |
Creates the first provider found, if there is one |
public static provider() |
A factory method that a provider class may declare instead of a public constructor without parameters |
META-INF/services/S |
A text file of provider class names, one per line, for plug-ins on the class path |
loader.reload() |
Clears the loader’s cache, so that providers installed since are found |
ServiceLoader.load(layer, S.class) |
Looks only in a module layer and its parent layers |
Check yourself
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
When plug-ins are created, and how to pick one without creating the others Pro: opens with a Pro, Premium or Ultimate pass
A plug-in’s constructor may open files, read settings or start threads, so creating every plug-in in order to use one of them is a real cost, paid at every start. This part measures what each way of using the loader creates, and reproduces a factory plug-in that a filter cannot see.
In this part:
- Three ways to use the loader, measured with constructor messages
- A factory plug-in that a filter cannot see, reproduced
4 runnable examples (Choose.java, EveryLoop.java, Java, Python, Wallet.java, WalletMethod.java) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Plug-ins on the class path, and every ServiceConfigurationError case reproduced Pro: opens with a Pro, Premium or Ultimate pass
Plug-ins also work without modules, and they fail in five ways that are worth recognising on sight. This part runs the same plug-ins from the class path, reproduces each failure with its exact message, and loads what works when one plug-in is broken.
In this part:
- The same plug-ins as plain JARs with META-INF/services
- Five mistakes and the messages they produce
- Loading what works and reporting the rest
3 runnable examples (Java, Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Exercise: a net-banking plug-in, and a service that can change without breaking plug-ins Pro: opens with a Pro, Premium or Ultimate pass
A plug-in system lives for years, and its service interface changes while old plug-ins stay in the field. This part reproduces what one wrong change does to an old plug-in, then gives you a plug-in to write and a rubric to check the design against.
In this part:
- A new method in the service, reproduced against an old plug-in
- The exercise: a net-banking plug-in
- Rubric
2 runnable examples (Limits.java, Python, abstract method, default method) · about 2 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
-
Fresher to mid · Warm-up
What are a service, a service provider and ServiceLoader in Java?
A service is an interface or abstract class that a program needs, and a service provider is an implementation of it that the program does not name in its code, often in a separate JAR called a plug-in. ServiceLoader, in java.util, finds the providers at run time: ServiceLoader.load(Service.class) returns a loader that you iterate, or stream to inspect providers before creating them. In modules, the host declares uses Service and each plug-in declares provides Service with its class; on the class path, a plug-in JAR lists its classes in META-INF/services/ followed by the service's full name. Providers are created lazily, in an order that is not defined, and the JDK uses the same mechanism, for example to find JDBC drivers.
-
Mid to senior
A plug-in JAR is on the module path, but ServiceLoader in your host finds no provider, and nothing fails. List what you check, in order, and how each cause shows itself.
-
Mid to senior
A desktop app loads 30 plug-ins through ServiceLoader when it starts, and starting takes four seconds; a typical session uses two of them. How do you cut the start-up cost without rewriting the plug-ins?
-
Senior to staff
Twelve plug-ins written by other teams implement your PaymentMethod interface. Every plug-in must now report a fee. How do you add that without breaking the twelve, and how should the host behave when one plug-in fails while loading?
Key takeaways
- A service is an interface; providers implement it;
ServiceLoaderfinds them at run time. - Modules declare
useson the host andprovides … with …on each plug-in; the host never requires a plug-in. - On the class path,
META-INF/services/<service name>lists the provider classes. - Providers are created lazily:
stream()withProvider.type()lets you choose before anything is created. - The order in which providers are found is not defined, so sort them or choose by name.
References
- Java SE 25 API: ServiceLoader (Oracle)
- Java SE 25 API: ServiceLoader.Provider (Oracle)
- Java SE 25 API: ServiceConfigurationError (Oracle)
- The Java Language Specification, Java SE 25: 7.7.3 and 7.7.4, uses and provides (Oracle)
- JEP 261: Module System (OpenJDK)
- Java SE 25 API: DriverManager (drivers loaded as service providers) (Oracle)
- The Java Language Specification, Java SE 25: 13.5.4 Interface Members (binary compatibility) (Oracle)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress