How Plugin Discovery Changed to Java Service Loader in 0.17.0
Plugin Discovery Mechanism
Kestra 0.17.0 uses a new mechanism to discover and load plugins. If you use custom plugins, follow this guide to make the necessary adjustments.
Plugins are now discovered and loaded using the standard Java Service Loader.
So far, Kestra heavily relied on the Bean Introspection mechanism provided by the Micronaut Framework for loading plugins (Micronaut is the JVM-based API framework used by Kestra).
However, this implementation encountered limitations in maintaining backward compatibility of plugins during major Micronaut version upgrades, and was limiting the ability to introduce future enhancements around the plugin mechanism.
This new implementation reduces the number of dependencies required for developing custom plugins, and plugins now load twice as fast.
Finally, this change is part of a wider effort to improve the developer experience around plugins, and to reduce Micronaut’s exposure outside the Kestra core.
This change introduces minor breaking changes to how custom plugins are built.
Below are the changes required to migrate to Kestra 0.17.0.
Micronaut Dependencies
For most plugin implementations, all Micronaut libs can be removed from the compileOnly dependencies in the build.gradle file.
However, Micronaut is still required to use the utility classes provided by Kestra for running unit-tests.
Kestra’s Annotation Processor
Kestra requires a new annotation processor to be configured in the build.gradle file of your project (or pom.xml for Maven).
annotationProcessor group: "io.kestra", name: "processor", version: kestraVersionThe role of this processor is to automatically manage the META-INF/services file needed by Java to discover your plugins.
Custom Validators
Kestra allows you to develop a custom constraint validator using the standard Java API for bean validation (i.e., JSR-380), which is used to validate the properties of custom tasks.
The custom validator must now implement the standard jakarta.validation.ConstraintValidator instead of the interface provided by Micronaut: io.micronaut.validation.validator.constraints.ConstraintValidator.
In addition, custom validation annotation should now strictly adhere to the Java bean specification — see the example below.
Kestra 0.16.6 and before:
@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = CustomNotEmptyValidator.class)public @interface CustomNotEmpty { String message() default "invalid";}import io.micronaut.validation.validator.constraints.ConstraintValidator;import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;// ...@Singleton@Introspectedpublic class CustomNotEmptyValidator implements ConstraintValidator<CustomNotEmpty, String> { @Override public boolean isValid( @Nullable String value, @NonNull AnnotationValue<CustomNotEmpty> annotationMetadata, @NonNull ConstraintValidatorContext context) { if (value == null) { return true; // nulls are allowed according to spec } else if (value.size() < 2) { context.messageTemplate("string must have at-least two characters"); return false; } else { return true; } }}Kestra 0.17.0 and later:
@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = CustomNotEmptyValidator.class)public @interface CustomNotEmpty { String message() default "invalid"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}import jakarta.validation.ConstraintValidator;import jakarta.validation.ConstraintValidatorContext;// ...@Singleton@Introspectedpublic class CustomNotEmptyValidator implements ConstraintValidator<CustomNotEmpty, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return true; // nulls are allowed according to spec } else if (value.size() < 2) { context.disableDefaultConstraintViolation(); context .buildConstraintViolationWithTemplate("string must have at-least two characters") .addConstraintViolation(); context.messageTemplate(); return false; } else { return true; } }}Was this page helpful?