How do the `uses` and `provides...with` directives implement the service-loader pattern in JPMS, and how does this relate to `ServiceLoader`?
answer
- uses S = consumer declares it will ServiceLoader.load(S)
- provides S with Impl = provider registers an implementation
- consumer does NOT requires the provider module
- ServiceLoader.load(S.class) returns all providers
- module-native replacement for META-INF/services
- provider needs public no-arg ctor or static provider()
basics
~20 suses S declares that your module consumes a service of interface type S via ServiceLoader. provides S with Impl declares that your module supplies Impl as an implementation of S. Together they let modules discover implementations without a hard dependency on the provider.
solid answer
~50 sJPMS has first-class support for the **service-provider pattern**. A *service* is an interface (or abstract class) S; *providers* supply implementations of it; *consumers* discover them at runtime via `java.util.ServiceLoader`. In `module-info.java`: a consumer writes `uses S` to declare it will look up implementations of S; a provider writes `provides S with com.acme.ImplA, com.acme.ImplB` to register its implementations. The consumer then calls `ServiceLoader.load(S.class)` and gets all registered providers — **without** ever `requires`-ing the provider modules. This achieves loose coupling: the consumer depends only on the service interface module, and providers can be added or swapped by changing the module path. It replaces the old `META-INF/services/<FQN>` files (which still work on the classpath, but `provides...with` is the module-native form). The provider class must be public with a public no-arg constructor, or expose a static `provider()` factory method returning S.
code
java · 22 lines// --- service interface module: com.acme.payment.api ---
module com.acme.payment.api {
exports com.acme.payment; // PaymentGateway interface
}
// --- provider module ---
module com.acme.payment.stripe {
requires com.acme.payment.api;
provides com.acme.payment.PaymentGateway
with com.acme.payment.stripe.StripeGateway;
}
// --- consumer module (no requires on the stripe module!) ---
module com.acme.app {
requires com.acme.payment.api;
uses com.acme.payment.PaymentGateway;
}
// consumer code:
ServiceLoader.load(PaymentGateway.class)
.findFirst()
.ifPresent(gw -> gw.charge(...));go deeper
Knows uses = consume a service, provides...with = supply one, tied to ServiceLoader.
Explains the decoupling (consumer doesn't requires the provider) and that uses is needed for discovery.
Details the provider-class requirements (no-arg ctor / provider() method), the relation to META-INF/services, and listing multiple impls.
Designs plugin architectures around services: stable service-interface modules, provider isolation, versioning, and deployment-time wiring via the module path.
## The problem: pluggable implementations You often want a consumer to use *some* implementation of an interface chosen at deployment time — a logging backend, a codec, a database driver — **without** the consumer code hard-depending on a specific implementation. This is the **service-provider / plugin pattern**. The pieces: - **Service**: an interface (or abstract class), the contract. Lives in a module both sides can read. - **Provider**: a concrete class implementing the service. - **Consumer**: code that discovers and uses providers at runtime. ## `java.util.ServiceLoader` — the runtime mechanism `ServiceLoader` is the JDK API that finds providers of a service at runtime: ```java ServiceLoader<PaymentGateway> loader = ServiceLoader.load(PaymentGateway.class); for (PaymentGateway gw : loader) { ... } // every registered provider ``` It loads each registered implementation lazily. Historically, providers registered themselves with a text file `META-INF/services/<fully.qualified.ServiceInterface>` listing implementation class names. That still works for classpath/legacy code. ## The module-native registration: `uses` and `provides...with` With JPMS, registration moves into `module-info.java`, where it is **type-checked and visible in the module graph**: **Consumer side — `uses S`:** ```java module com.acme.app { requires com.acme.payment.api; // to name the service type uses com.acme.payment.PaymentGateway; } ``` `uses S` declares: "this module calls `ServiceLoader.load(S)` and should be able to discover providers of S." Without `uses`, `ServiceLoader.load` returns nothing for module-path providers — the directive is required for discovery to work. **Provider side — `provides S with Impl`:** ```java module com.acme.payment.stripe { requires com.acme.payment.api; provides com.acme.payment.PaymentGateway with com.acme.payment.stripe.StripeGateway; } ``` `provides S with Impl1, Impl2` registers one or more implementation classes for service S. You can list several after `with`. ## The key payoff: no direct dependency Notice the consumer **does not `requires` the provider module** (`com.acme.payment.stripe`). It only requires the *service interface* module. The provider is wired in purely by being **present on the module path**. This is the whole point — the consumer is decoupled from concrete providers, so you can: - add a new provider by dropping its module on the path, - swap providers without recompiling the consumer, - ship zero providers and have the consumer get an empty list. The module system itself wires `provides` to `uses` at resolution time. ## Requirements on the provider class For `ServiceLoader` to instantiate a provider, the named `with` class must either: 1. be **public** with a **public no-argument constructor**, OR 2. expose a **public static `provider()` method** (no args) returning an instance assignable to S (the *provider-method* form, useful when construction needs logic or the returned type differs). The provider class needn't be in an *exported* package — `ServiceLoader` is given special access to instantiate it, which is part of why the module-native form is cleaner than opening packages. ## Relationship summary | Concept | Directive / API | Role | |---|---|---| | Declare consumption | `uses S` | consumer will ServiceLoader.load(S) | | Declare provision | `provides S with Impl` | provider registers Impl for S | | Runtime lookup | `ServiceLoader.load(S.class)` | returns all registered providers | | Legacy registration | `META-INF/services/<S>` | classpath equivalent of provides | ## Gotchas - Forgetting `uses` → `ServiceLoader` finds nothing on the module path. - The service interface must be readable by both sides (both `requires` its module). - A provider with no public no-arg constructor and no `provider()` method fails at load time.
- Does the consumer module need to `requires` the provider module?No. The consumer only requires the service-interface module and declares `uses S`. Providers are discovered by being present on the module path via their `provides...with` declarations, which is exactly the decoupling the pattern delivers.
- What are the requirements on a class named after `with`?It must be public with a public no-arg constructor, or expose a public static `provider()` method returning the service type. Otherwise ServiceLoader cannot instantiate it and fails at load time.
uses is posting a job description for a role (the service interface). provides...with is a candidate handing in a resume for that role. ServiceLoader is HR that hands you every applicant at runtime — and you never had to know any candidate's name in advance.
saying these in an interview costs you the question
- Believing the consumer must requires the provider module
- Forgetting `uses`, then wondering why ServiceLoader finds nothing
- Thinking provides...with replaces requires for the service interface
- Assuming the provider package must be exported/opened (ServiceLoader gets special access)