Avaje SPI

Discord Source API Docs Issues Releases
Discord Github Javadoc Github



The Java Service Provider Interface uses configuration files to find and load concrete implementations of given service provider interfaces. The downside of this system is that configuration files are not checked by the java compiler. As such it is a common occurence to forget to add the correct entries.

This zero-dependency library uses annotation processing to automatically generate the required META-INF/services entries for annotated classes. In addition, it will validate that the required provides clauses are present in an application's module-info files.

Dependency

To use this library add the avaje-spi-service as a provided/optional dependency. (The dependencies need only be included at compile-time and are not required at runtime)

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-spi-service</artifactId>
  <version>1.2</version>
  <optional>true</optional>
  <scope>provided</scope>
</dependency>

JDK 23+ note:

In JDK 23+, annotation processors are disabled by default, so we need to add a compiler property to re-enable.

<properties>
  <maven.compiler.proc>full</maven.compiler.proc>
</properties>

@ServiceProvider

On classes that you'd like registered, place the @ServiceProvider annotation. As long as you only have one interface/ superclass, that type is assumed to be the SPI interface. So given the example below:

@ServiceProvider
public class BassServiceProvider implements MusicService {
  ...
}

Multi-Interfaced classes

If you have multiple interfaces and/or a base type, the library cannot infer the contract type. In such a case, we specify the contract type explicitly in the @ServiceProvider:

@ServiceProvider(MusicService.class)
public class BassServiceProvider extends Instrument implements Stringed, MusicService {
  ...
}

Module Validation

For modular projects, the processor will validate that all the required provides clauses are accounted for. A compile error describing what `provides` statements are missing will be throw if validations fail. So given the following class and module-info:

@ServiceProvider
public class BassServiceProvider implements MusicService {
  ...
}
module my.module {

  requires static io.avaje.spi;
  //provides not defined
}

You will get the compile error:

  Compilation failure /src/main/java/module-info.java:[1,1]
  Missing `provides MusicService with BassServiceProvider;`

@Service

As a library developer, should you wish to define an spi interface for multiple layers of extension, the @Service annotation will help ensure those using @ServiceProvider will automatically register the correct top-level type.

Consider the example of avaje-validaton:

/** Super interface for all validation SPIs */
@Service
public sealed interface ValidationExtension
    permits AdapterFactory,
        AnnotationFactory,
        GeneratedComponent,
        MessageInterpolator,
        ValidatorCustomizer {}

To reduce calls to the ServiceLoader, avaje-validation only loads the top level ValidationExtension interface instead of loading the individual interfaces. The presence of the @Service annotation ensures the below will be registered correctly.

//this class is registered as a ValidationExtension rather than a ValidatorCustomizer
@ServiceProvider
public class Customizer implements ValidatorCustomizer {
  ....
}