kestra-io / kestra-io/plugin-ai
Add the Jackson 3 label serde annotations to KestraFlow for Kestra's Micronaut 5 bump
- Dominant language
- Java
- Stars
- 9
- Forks
- 24
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 15
Description
### Context
Kestra core is moving to Micronaut 5 (kestra-io/kestra#18138). Kestra's own `JacksonMapper` stays on **Jackson 2**, while Micronaut 5 serializes and deserializes HTTP bodies with **Jackson 3** (`tools.jackson`).
Micronaut 5 ships a Jackson 2 annotation compatibility layer (`Jackson2AnnotationSupport`), enabled by default, that keeps `@JsonSerialize(as=)`, `@JsonDeserialize(as=)`, `@JsonDeserialize(builder=)`, `typing=` and `@JsonPOJOBuilder` working. It does **not** cover `using=`, and cannot: `using=` names a `com.fasterxml.jackson.databind.JsonDeserializer` / `JsonSerializer` implementation, and there is no Jackson 3 annotation attribute that can point at a Jackson 2 class. The compat layer translates annotations attribute-by-attribute through a dynamic proxy, so it deliberately leaves `findSerializer`/`findDeserializer` alone rather than handing Jackson 3 a class it cannot instantiate.
The consequence is that a Jackson 2 `using=` annotation is **silently ignored** by the mapper that binds Kestra's HTTP bodies — no warning, no error on the serialize side.
### What to change
`src/main/java/io/kestra/plugin/ai/tool/KestraFlow.java` carries the Jackson 2 label serde pair:
```java
@JsonSerialize(using = ListOrMapOfLabelSerializer.class)
@JsonDeserialize(using = ListOrMapOfLabelDeserializer.class)
private List<@NoSystemLabelValidation Label> labels;
```
Add the Jackson 3 twins alongside, mirroring what core now does on `AbstractFlow` (and on `Execution`, `AbstractTrigger`, `Backfill`, `SLA`, `Subflow`):
```java
@JsonSerialize(using = ListOrMapOfLabelSerializer.class)
@JsonDeserialize(using = ListOrMapOfLabelDeserializer.class)
@tools.jackson.databind.annotation.JsonSerialize(using = Jackson3ListOrMapOfLabelSerializer.class)
@tools.jackson.databind.annotation.JsonDeserialize(using = Jackson3ListOrMapOfLabelDeserializer.class)
private List<@NoSystemLabelValidation Label> labels;
```
`Jackson3ListOrMapOfLabelSerializer` and `Jackson3ListOrMapOfLabelDeserializer` live in `io.kestra.core.serializers` and ship with the core version that carries the Micronaut 5 bump, so this is purely additive. **Keep the Jackson 2 pair** — that is what parses flow YAML.
### Impact — low today, worth doing with the core bump
- **The deserializer is the half that matters.** `ListOrMapOfLabelDeserializer` accepts labels written either as a map (`labels: {env: prod}`) or as a list of key/value pairs and normalizes both to `List`. Without the Jackson 3 twin, Jackson 3 falls back to its default `List` deserializer and the map form fails with a `MismatchedInputException`.
- **The serializer twin is cosmetic.** `ListOrMapOfLabelSerializer` is a pass-through that delegates to the `List` or `Map` serializer; since the field is declared `List`, Jackson 3's default output is identical. Add it for symmetry with core rather than for a behaviour change.
- **Not reachable today.** Flows are parsed from YAML by Kestra's Jackson 2 mapper, and no Kestra API endpoint currently binds a task from a JSON body — every `@Body` parameter in `webserver` and `webserver-ee` was checked. This becomes reachable the moment one does.
### No change needed for the `PluginDeserializer` sites
These six all declare `@JsonDeserialize(using = PluginDeserializer.class)` on an abstract `AdditionalPlugin` subtype:
- `io/kestra/plugin/ai/domain/ModelProvider.java`
- `io/kestra/plugin/ai/domain/ToolProvider.java`
- `io/kestra/plugin/ai/domain/ContentRetrieverProvider.java`
- `io/kestra/plugin/ai/domain/EmbeddingStoreProvider.java`
- `io/kestra/plugin/ai/domain/MemoryProvider.java`
- `io/kestra/plugin/ai/domain/Observability.java`
They hit the same `using=` gap, but it is fixed **in core**, not here: `Jackson3PluginModule` now matches any abstract subtype of `AdditionalPlugin` by assignability and routes it through the Jackson 3 plugin deserializer. A plugin's extension-point base type is plugin-defined, so core cannot enumerate these the way it enumerates `Task`, `AbstractTrigger` and friends — assignability is the only mechanism that works without every plugin shipping a Jackson 3 deserializer.
So keep those annotations exactly as they are: Jackson 2 still needs them for the YAML path, and the documented contract in the comment above each one ("The abstract plugin base class must define using the PluginDeserializer, AND concrete subclasses must be annotated by `@JsonDeserialize()` to avoid StackOverflow") remains correct.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/main/java/io/kestra/plugin/ai/tool/KestraFlow.java and compare its labels annotations with core's AbstractFlow, using Jackson3ListOrMapOfLabelSerializer and Jackson3ListOrMapOfLabelDeserializer from io.kestra.core.serializers as the reference. Keep the existing Jackson 2 annotations for YAML and add the Jackson 3 serializer and deserializer annotations. Done means Jackson 3 can bind map-form and list-form labels while the Jackson 2 path remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100