33.1 ServiceLoader And Provider Model

Discover and load service implementations dynamically using ServiceLoader for extensible, plugin-based architectures.

Service Interface

package com.example.api;

public interface StorageProvider {
  void save(String key, byte[] data);
  byte[] load(String key);
}

Provider Implementation

package com.example.impl;

import com.example.api.StorageProvider;

public class FileStorageProvider implements StorageProvider {
  public void save(String key, byte[] data) { /* write to file */ }
  public byte[] load(String key) { /* read from file */ return new byte[0]; }
}

Module-Based Service Declaration

api module (module-info.java):

module com.example.api {
  exports com.example.api;
  uses com.example.api.StorageProvider;
}

impl module (module-info.java):

module com.example.impl {
  requires com.example.api;
  provides com.example.api.StorageProvider with com.example.impl.FileStorageProvider;
}

Loading Services

import java.util.ServiceLoader;

ServiceLoader<StorageProvider> loader = ServiceLoader.load(StorageProvider.class);
for (StorageProvider provider : loader) {
  provider.save("key1", "data".getBytes());
}

Or stream-based:

StorageProvider provider = ServiceLoader.load(StorageProvider.class)
    .findFirst()
    .orElseThrow();

Classpath-Based Services (Legacy)

Create META-INF/services/com.example.api.StorageProvider:

com.example.impl.FileStorageProvider

Guidance

  • Use module provides for clear, compile-time-checked service declarations.
  • Prefer ServiceLoader.load(Class, ModuleLayer) for explicit layer control.
  • Services enable plugin architectures without coupling to implementations.
  • Combine with module layers for runtime feature loading.