Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Advanced
  • 22 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 22 minutes free
Free: A service and two providers · Loading the providers at run time · The ServiceLoader API in one table · Check yourself. With a pass: 3 in-depth parts and 3 interview answers, about 15 minutes.

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

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:

The checkout uses PaymentMethod; ServiceLoader finds the two plug-in modules that provide it at run time; all require kirana.pay.kirana.checkoutuses PaymentMethodServiceLoader.load(PaymentMethod.class)at run timePlug-ins found at run timekirana.payexports the interfacePaymentMethodkirana.upiprovides PaymentMethodwith ...upi.Upikirana.cardprovides PaymentMethodwith ...card.Cardasks for providersfinds every modulethat provides itboth require

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.

  1. kirana.checkout declares uses PaymentMethod. When it runs, it calls ServiceLoader.load(PaymentMethod.class).
  2. 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.
  3. 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.
  4. 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

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

Two plug-ins found at run time, then one, with the checkout unchanged Python · plugins_session.py
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-module shows provides com.example.kirana.pay.PaymentMethod with com.example.kirana.upi.Upi in 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.

  1. Question 1 of 5 In the lesson, which module declares uses com.example.kirana.pay.PaymentMethod?

    Choose one answer.

    Show the answer to question 1

    Answer: kirana.checkout, the module that calls ServiceLoader.load

    uses goes on the module that looks providers up; each plug-in declares provides … with …; the service module only exports the interface.

  2. Question 2 of 5 Should kirana.checkout declare requires kirana.upi?

    Choose one answer.

    Show the answer to question 2

    Answer: No: the checkout requires only the service module, so plug-ins can be added or removed without changing it

    The lesson's second run left the card plug-in out and the checkout still worked, unchanged. Requiring a plug-in would tie the host to it, which is what a service avoids.

  3. Question 3 of 5 You want the provider whose class carries @Named("UPI"), without creating any other provider. Which approach does that?

    Choose one answer.

    Show the answer to question 3

    Answer: loader.stream(), filter on provider.type(), then get() on the match

    stream() yields Provider objects, and Provider.type() gives the class without creating an instance. A loop creates every provider it passes on the way to the match, and findFirst() takes whichever comes first.

  4. Question 4 of 5 Two plug-in modules provide the same service. In which order does ServiceLoader find them?

    Choose one answer.

    Show the answer to question 4

    Answer: The order is not defined; sort the providers or choose them by name

    The ServiceLoader documentation leaves the order of modules in a layer undefined, which is why the lesson's checkout sorts by class name before creating anything.

  5. Question 5 of 5 A plain JAR on the class path provides com.example.kirana.pay.PaymentMethod with com.example.cash.Cash. What does it contain for ServiceLoader to find Cash?

    Choose one answer.

    Show the answer to question 5

    Answer: A file META-INF/services/com.example.kirana.pay.PaymentMethod whose line is com.example.cash.Cash

    The provider-configuration file is named after the service and lists provider classes, one per line. Explicit modules ignore such files and use provides instead.

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.

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.

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.

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).

  1. 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.

  2. 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.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. 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?

    Model answer, Pro. First step and follow-ups

  4. 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?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A service is an interface; providers implement it; ServiceLoader finds them at run time.
  • Modules declare uses on the host and provides … 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() with Provider.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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.