Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java ServiceLoader: provider configuration and lazy discovery

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

ServiceLoader discovers implementations of a service interface from provider configuration visible to a chosen class loader.

Download Java source kit

This program targets Java 8 without preview flags. Use a full JDK for compiler-based examples.

Registration belongs to the provider package

A report exporter depends on an interface rather than a hard-coded implementation. On the class path, the provider lists its binary class name in META-INF/services followed by the interface binary name. A named module instead has uses and provides declarations; mixing the two layouts without checking deployment can produce an empty discovery result.

This fixture creates only the provider configuration in a temporary directory. The implementation class is already compiled with the program. A child URLClassLoader exposes the resource and delegates the implementation class to its parent, so the contract is tested without downloading or loading an unknown plugin.

Iteration can fail

Provider lookup and instantiation happen lazily. A configured class can be absent, incompatible, or unable to construct, and iteration can throw ServiceConfigurationError. Do not treat the first successfully returned provider as proof that the rest of the file is valid.

The fixture finds one exporter and calls its operation. Discovery does not register a priority or select the best provider for a workload. If selection depends on format, explicitly inspect a capability contract and reject ambiguous matches. ServiceLoader instances also need their own concurrency ownership; they are not general-purpose thread-safe registries.

Working program

Java
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ServiceLoader;
public class ReceiptProviderDiscovery {
    public interface Exporter { String format(); }
    public static class TextExporter implements Exporter {
        public TextExporter() {}
        public String format() { return "receipt-text"; }
    }
    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("receipt-provider-");
        Path services = Files.createDirectories(root.resolve("META-INF/services"));
        Path config = services.resolve(Exporter.class.getName());
        try {
            Files.write(config, (TextExporter.class.getName()+"\n").getBytes(StandardCharsets.UTF_8));
            try (URLClassLoader loader = new URLClassLoader(new URL[]{root.toUri().toURL()}, ReceiptProviderDiscovery.class.getClassLoader())) {
                int count=0;
                for (Exporter exporter : ServiceLoader.load(Exporter.class, loader)) {
                    System.out.println(exporter.format()); count++;
                }
                System.out.println("providers="+count);
            }
        } finally {
            Files.deleteIfExists(config); Files.deleteIfExists(services);
            Files.deleteIfExists(root.resolve("META-INF")); Files.deleteIfExists(root);
        }
    }
}

Output

Output
receipt-text
providers=1

Costs and boundaries

Resource discovery and provider construction depend on the number of registrations and provider behavior. The program retains one provider and closes its own class loader. Closing a loader does not interrupt running plugin threads or provide a security boundary. Loading untrusted code requires a separate isolation design.

Common Mistakes

  • Use binary names in class-path provider files, including a dollar sign for nested types.
  • Do not assume provider iteration order expresses application priority.
  • Do not suppress every ServiceConfigurationError and quietly run with a partial provider set.

Read next

Class loader ownership, Named modules, Interface contracts.

java
service-discovery
Storage details