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