DEV Community

Cover image for Java Service Provider Interface (SPI), what is it and how to use it
Carlos Monteiro
Carlos Monteiro

Posted on • Edited on

Java Service Provider Interface (SPI), what is it and how to use it

As a software developer, it's not always possible to dive deep into the language you're working with, and sometimes it's difficult to find situations where you need to use advanced or non-trivial features. It's easy to use a magic framework, the hard part is having the necessary knowledge to create it. For this reason, I created the tag #javaunderthehood to gather this content and help other people understand how this magic happens. Feel free to join me with an interesting new topic.


What is the Service Provider Interface (SPI)?


Introduced in Java 6, the Service Provider Interface (SPI) provides the ability to extend application functionality by adding service implementations that can modify, replace or extend the original behavior of an application.


What is ServiceLoader java class?


The ServiceLoader class is the heart of this feature. What does the documentation say about the ServiceLoader class?

A facility to load implementations of a service.
A service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class. A ServiceLoader is an object that locates and loads service providers deployed in the run time environment at a time of an application's choosing. Application code refers only to the service, not to service providers, and is assumed to be capable of choosing between multiple service providers (based on the functionality they expose through the service), and handling the possibility that no service providers are located.

In general, the service is an interface that you can use to implement your own provider. If this service is part of a dependency of your project and this dependency gives you the possibility, you can write your own provider that fits your needs and the library will be able to use it.


How to use it?


Let's start by creating our service, which will be an interface that will allow us to create implementations to proccess different types of images.

public interface ImageProcessorService {  
    String getType();  
    String process();  
}
Enter fullscreen mode Exit fullscreen mode

Now we will create two implementations of it, which will be responsible for proccessing PNG and JPG images.

PNG

public final class PNGImageProcessorProvider implements ImageProcessorService {
  @Override
  public String getType() {
    return BasicImageType.PNG.name();
  }

  @Override
  public void process() {
    System.out.println("Processing PNG image");
  }
}
Enter fullscreen mode Exit fullscreen mode

JPG

public class JPGImageProcessorProvider implements ImageProcessorService {
  @Override
  public String getType() {
    return BasicImageType.JPG.name();
  }

  @Override
  public void process() {
    System.out.println("Processing JPG image");
  }
}
Enter fullscreen mode Exit fullscreen mode

In order for ServiceLoader to load our provider, we need to create a package called META-INF in our resource repository. This folder must contain a subfolder called services where we need to create a file with the reference to our service.

The appearance of our project looks something like this at the moment:

.
├── java
│   └── com
│       └── github
│           └── c4rlosmonteiro
│               └── imageprocessorservicespi
│                   ├── BasicImageType.java
│                   ├── provider
│                   │   ├── JPGImageProcessorProvider.java
│                   │   └── PNGImageProcessorProvider.java
│                   └── service
│                       └── ImageProcessorService.java
└── resources
    └── META-INF
        └── services
            └── com.github.c4rlosmonteiro.imageprocessorservicespi.service.ImageProcessorService

Enter fullscreen mode Exit fullscreen mode

Now you need to modify the file com.github.c4rlosmonteiro.imageprocessorservicespi.service.ImageProcessorService and add the references to all the providers you want to load.

    com.github.c4rlosmonteiro.imageprocessorservicespi.provider.JPGImageProcessorProvider  
    com.github.c4rlosmonteiro.imageprocessorservicespi.provider.PNGImageProcessorProvider
Enter fullscreen mode Exit fullscreen mode

To see the result of our implementation, we need to finally use the ServiceLoader class to load our providers.

public class ImageProcessorApp {
  private final List<ImageProcessorService> imageProcessorProviders;

  public ImageProcessorApp() {
    final ServiceLoader<ImageProcessorService> imageProcessorServiceLoader = ServiceLoader.load(ImageProcessorService.class);
    final Iterator<ImageProcessorService> imageProcessorServiceIterator = imageProcessorServiceLoader.iterator();

    this.imageProcessorProviders = new ArrayList<>();

    while (imageProcessorServiceIterator.hasNext()) {
      imageProcessorProviders.add(imageProcessorServiceIterator.next());
    }
  }

  public void processImages(final String type) {
    for (final ImageProcessorService imageProcessorProvider : imageProcessorProviders) {
      if (Objects.equals(imageProcessorProvider.getType(), type)) {
        imageProcessorProvider.process();
        return;
      }
    }

    throw new RuntimeException("No provider found to process image type: " + type + ". Create a new provider for it!");
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, we create a simple main class and execute our code.

public class Main {
  public static void main(final String[] args) {
    final ImageProcessorApp imageProcessorApp = new ImageProcessorApp();
    imageProcessorApp.processImages(BasicImageType.PNG.name());
    imageProcessorApp.processImages(BasicImageType.JPG.name());
    imageProcessorApp.processImages("MY_CUSTOM_OR_NOT_SUPPORTED_IMAGE_TYPE");
  }
}
Enter fullscreen mode Exit fullscreen mode

Run and check the logs.

Processing PNG image
Processing JPG image
Exception in thread "main" java.lang.RuntimeException: No provider found to process image type: MY_CUSTOM_OR_NOT_SUPPORTED_IMAGE_TYPE. Create a new provider for it!
    at com.github.c4rlosmonteiro.imageprocessorservicespi.ImageProcessorApp.processImages(ImageProcessorApp.java:30)
    at com.github.c4rlosmonteiro.imageprocessorservicespi.Main.main(Main.java:8)

Enter fullscreen mode Exit fullscreen mode

Check the source code on github.com.


Before you go!

Follow for more ;)
Suggest new topics
Use the comments section to discuss the feature and >comment on real-world use cases to help others understand >its usage


#javaunderthehood

Top comments (0)