Java SPI (Service Provider Interface) and ServiceLoader

Filed Under: Java

Java SPI (Service Provider Interface) is the mechanism to load services dynamically. We can implement Java SPI in our application by following the specific set of rules and load the services using the ServiceLoader class.


Java SPI Components

There are four components in the SPI implementation.

  1. Service Provider Interface: An interface or abstract class that defines the contract for the service provider implementation classes.
  2. Service Providers: The implementation classes that actually provides the services.
  3. SPI Configuration File: A special file that provides the logic to look for the services implementations. The file name must be present in the META-INF/services directory. The file name should be exactly same as the service provider interface fully qualified name. Every line in the file have one implementation service class details, again the fully qualified name of the service provider class.
  4. ServiceLoader: The Java SPI main class that is used to load the services for a service provider interface. There are various utility methods in the ServiceLoader to get specific implementations, iterate through them, or reload the services again.

Java Service Provider Interfaces Examples

The java.util.spi package provides a lot of service provider interfaces that can be implemented to provide services.

  1. ResourceBundleProvider, ResourceBundleControlProvider, and AbstractResourceBundleProvider: Service Provider interfaces and abstract class for Resource Bundle implementation classes.
  2. LocaleServiceProvider, CalendarDataProvider, CalendarNameProvider, CurrencyNameProvider, TimeZoneNameProvider, and LocaleNameProvider: for implementing Locale specific service providers.
Java SPI Interfaces List

Java SPI Interfaces List


Java SPI Example

Let’s create an implementation of SPI and load some services using the ServiceLoader class.


1. Service Provider Interface

Let’s say we have a MessageServiceProvider interface that defines the contract for the service provider implementations.


package com.journaldev.serviceproviders;

public interface MessageServiceProvider {

	void sendMessage(String message);
}

2. Service Provider Implementation Classes

We want to support email messages and push notification messages. So we will create two service provider implementations of MessageServiceProvider interface – EmailServiceProvider and PushNotificationServiceProvider.


package com.journaldev.serviceproviders;

public class EmailServiceProvider implements MessageServiceProvider {

	public void sendMessage(String message) {
		System.out.println("Sending Email with Message = "+message);
	}

}

package com.journaldev.serviceproviders;

public class PushNotificationServiceProvider implements MessageServiceProvider {

	public void sendMessage(String message) {
		System.out.println("Sending Push Notification with Message = "+message);
	}

}

3. Service Provider Configuration File

The configuration file has to be created in the META-INF/services directory. Its name should be “com.journaldev.serviceproviders.MessageServiceProvider“. We will specify both the implementation classes in this file.


com.journaldev.serviceproviders.EmailServiceProvider
com.journaldev.serviceproviders.PushNotificationServiceProvider

4. ServiceLoader Example to Load Services

Finally, we have to load the services using the ServiceLoader class. Here is a simple test program showing its usage.


package com.journaldev.test;

import java.util.Optional;
import java.util.ServiceLoader;

import com.journaldev.serviceproviders.MessageServiceProvider;

public class ServiceLoaderTest {

	public static void main(String[] args) {
		ServiceLoader<MessageServiceProvider> serviceLoader = ServiceLoader.load(MessageServiceProvider.class);

		for (MessageServiceProvider service : serviceLoader) {
			service.sendMessage("Hello");
		}

		// using Java 8 Optional to get the first service
		Optional<MessageServiceProvider> firstService = serviceLoader.findFirst();
		if (firstService.isPresent()) {
			firstService.get().sendMessage("Hello Friend");
		}

		// using Java 8 forEach() method
		serviceLoader.forEach((service) -> service.sendMessage("Have a Nice Day!"));

		// Total Number of Loaded Services
		System.out.println(serviceLoader.stream().count());

	}

}

When we run the above program, we get the following output.


Sending Email with Message = Hello
Sending Push Notification with Message = Hello
Sending Email with Message = Hello Friend
Sending Email with Message = Have a Nice Day!
Sending Push Notification with Message = Have a Nice Day!
2

The below image shows our final project structure and SPI components.

Java SPI Example Project Structure

Java SPI Example Project Structure


ServiceLoader Important Methods

Let’s look at the important methods of the ServiceLoader class.

  • load(): The static method to load the services of a particular SPI.
  • findFirst(): returns the first service available for this service provider.
  • forEach(): useful to run some code for every service provider in this service loader instance.
  • stream(): returns the stream of the service providers in this service loader.
  • iterator(): returns an iterator of the service providers.
  • reload(): reloads the service providers. It’s useful when we change the service provider configurations on the fly and want to reload the services list.

Conclusion

Java SPI provides an easy way to dynamically configure and load the services in our application. However, it depends a lot on the service configuration file and any change in the file can break the application.


References


You can checkout the complete project from our GitHub Repository.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages