-
Notifications
You must be signed in to change notification settings - Fork 49
Description
Summary
The SDK currently keeps a private, static ObjectMapper
inside io.serverlessworkflow.impl.jackson.JsonUtils
:
public class JsonUtils {
private static ObjectMapper mapper = new ObjectMapper();
public static ObjectMapper mapper() { return mapper; }
}
Because the field is private and there is no setter/SPI, applications cannot replace this mapper with their own preconfigured instance. This makes it difficult to align serialization with application-level Jackson settings (e.g., Java Time, Kotlin module, custom serializers, property naming, etc.).
Motivation
Real-world workflows often publish lifecycle CloudEvents whose payloads contain:
- Java 8 date/time types (
OffsetDateTime
,Instant
) - Domain POJOs that rely on custom serializers/deserializers or naming strategies
- Additional Jackson modules (e.g.,
jackson-datatype-jsr310
,jackson-module-parameter-names
, Kotlin, Afterburner)
Without a way to supply a customized ObjectMapper
, users may encounter exceptions such as:
InvalidDefinitionException: Java 8 date/time type `java.time.OffsetDateTime`
not supported by default: add Module "jackson-datatype-jsr310"...
They can’t swap in an app-configured mapper; they can only call JsonUtils.mapper()
and try to register modules on the singleton—if they even know to do so, and only if that aligns with their lifecycle.
Current Behavior
JsonUtils
creates and owns a staticObjectMapper
.- There is no public mechanism to replace or configure it (beyond calling
mapper().registerModule(...)
at runtime, which is not always ideal and may be too late in some environments). - The lifecycle publisher (
JacksonLifeCyclePublisher
) depends onJsonUtils.mapper()
for serialization.
Proposal
Add a supported mechanism to customize the ObjectMapper
used by the SDK:
Option A (minimal & straightforward)
Add a public setter:
public final class JsonUtils {
private static volatile ObjectMapper mapper = new ObjectMapper();
public static ObjectMapper mapper() { return mapper; }
public static void setMapper(ObjectMapper custom) {
if (custom == null) throw new IllegalArgumentException("mapper must not be null");
mapper = custom; // safe publication via volatile
}
}
Pros: Simple, non-breaking, easy to document ("call early at bootstrap").
Cons: Global singleton; requires users to manage call order.
Option B (SPI-based configuration)
Introduce an SPI that can provide or configure the mapper at startup:
public interface JacksonMapperCustomizer {
ObjectMapper customize(ObjectMapper defaultMapper);
}
Load via ServiceLoader
once on first access:
static {
mapper = new ObjectMapper();
ServiceLoader.load(JacksonMapperCustomizer.class).forEach(c -> mapper = c.customize(mapper));
}
Pros: Plays well with framework integrations; no explicit “set” calls; supports composition.
Cons: Slightly more complex; still a global singleton.
Option C (Builder-level override)
Expose WorkflowApplication.Builder.withObjectMapper(ObjectMapper)
and use it across the SDK (including JsonUtils
).
Pros: Clear ownership via builder; test-friendly.
Cons: Larger refactor if JsonUtils
is used widely outside WorkflowApplication
.
Backward Compatibility
- Default behavior remains unchanged if users do nothing.
- Existing code that calls
JsonUtils.mapper()
continues to work. - Option A is the least invasive; Option B/C provide better integration but require more changes.
Alternatives Considered
-
Instruct users to call
JsonUtils.mapper().findAndRegisterModules()
at runtime.
Works in some cases, but not always feasible (order of initialization, multiple mappers in the app, etc.). -
Reflection to replace the private field.
Brittle, not portable, problematic in native images/JPMS.
Example Use Cases
- Register
jackson-datatype-jsr310
to serializeOffsetDateTime
:
ObjectMapper om = new ObjectMapper()
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
JsonUtils.setMapper(om);
- Provide a shared, application-configured
ObjectMapper
(property naming strategy, polymorphic typing, custom serializers) to ensure CE data mirrors the app’s JSON contract.
Acceptance Criteria
- A documented, public, supported way to override or customize the
ObjectMapper
used by the SDK. - Unit test demonstrating that lifecycle CloudEvent payloads with
OffsetDateTime
are serialized without error when a mapper withjackson-datatype-jsr310
is supplied. - No behavioral change for users who don’t opt in.
Thanks for considering! This change would make the SDK much easier to integrate into applications with established Jackson configuration.