The Feature Context is the mechanism in the core lib module for safely
rolling out new parsing and modelling behaviours. It is completely separate from the
Wiki Markup Conversion Feature Flags,
which serve a similar purpose for the wiki module.
The Feature enum (in com.atlassian.adf.ctx) defines named feature flags for the core library. Each Feature is either enabled or disabled for the current thread. The enabled state is controlled via a thread-local Context object, which holds a FeatureProvider that answers feature enablement queries.
This design means that different threads can have different feature states simultaneously, and the state is automatically scoped to a single conversion call without leaking between calls.
Inside library code, a feature is checked by calling Feature.XYZ.enabled():
1 2 3 4if (Feature.PARSE_ENABLE_REPAIRS.enabled()) { // tolerate and silently repair certain malformed inputs }
This reads the current thread's Context and asks its FeatureProvider whether the feature is enabled. If no Context has been established on the current thread, all features default to a state that is defined by the flag’s definition, which for most flags is disabled by default, but may express a dependency on another flag’s state or default to enabled for a flag that is deprecated anyway.
Callers enable features by wrapping their conversion call in a context:
1 2 3 4 5 6 7 8 9 10 11// Enable a single feature Context.withFeature(Feature.PARSE_ENABLE_REPAIRS).run(() -> { Doc doc = adf.parseDoc(json); }); // Enable multiple features FeatureSet features = FeatureSet.of(Feature.PARSE_ENABLE_REPAIRS, Feature.PARSE_REPAIR_EMPTY_TEXT); Context.with(features, () -> { Doc doc = adf.parseDoc(json); });
The context is restored to its previous state when the block exits, even if an exception is thrown.
The following features are currently defined. Features marked as deprecated are no-ops — their behaviour has become unconditional and the flag will be removed in a future major release.
| Feature | Default | Description |
|---|---|---|
PLAINTEXT_MENTION_USE_ID | Off | Changes how mentions are rendered in plain-text contexts. When enabled, a mention with no display text uses the account ID instead of a generic fallback. |
ALLOW_NESTED_LISTS_AS_FIRST_CHILD(deprecated) | N/A | Originally controlled whether a nested list could appear as the first content item in a listItem. The rollout of ADF Change 94 (list indentation flexibility) is now complete and this is always permitted. This flag is a no-op. |
PARSE_ENABLE_REPAIRS | Off | Enables tolerant parsing. When active, certain malformed inputs that would normally throw a parse exception are silently repaired instead. Enable with care — repairs may change the semantics of the input. |
PARSE_REPAIR_EMPTY_TEXT | Follows PARSE_ENABLE_REPAIRS | When enabled, empty text nodes (which are technically invalid ADF) are silently dropped during parsing rather than causing an exception. This feature is automatically enabled whenever PARSE_ENABLE_REPAIRS is enabled. |
CONVERT_AS_MEDIA_INLINE | Off | Allows mediaInline nodes to be produced by format conversion modules. When disabled, converters fall back to mediaSingle. |
FIX_COLOR_PARSING | Off | Extends the CSS color formats accepted by the textColor mark parser to include 8-digit hex (#RRGGBBAA), rgba(), and percentage-based rgb() values, in addition to the standard 6-digit hex and named colors. |
DATE_USE_EPOCH_MILLIS | Off | Changes the format of date node timestamps from ISO-8601 strings to epoch milliseconds (as a number). Enable this if your downstream system expects the numeric format. |
Features can depend on each other. A dependent feature is automatically enabled whenever its parent is enabled, even if it was not explicitly included in the FeatureSet:
1 2 3 4 5// PARSE_REPAIR_EMPTY_TEXT is automatically enabled by PARSE_ENABLE_REPAIRS Context.withFeature(Feature.PARSE_ENABLE_REPAIRS).run(() -> { // Both PARSE_ENABLE_REPAIRS and PARSE_REPAIR_EMPTY_TEXT are active here });
Dependencies are declared in the Feature enum via a BooleanSupplier constructor argument: PARSE_REPAIR_EMPTY_TEXT(PARSE_ENABLE_REPAIRS::enabled).
For advanced use cases — such as reading feature states from a remote feature flag service — you can supply a custom FeatureProvider to the context:
1 2 3 4 5FeatureProvider myProvider = feature -> myFlagService.isEnabled(feature.name()); Context.withFeatureProvider(myProvider).run(() -> { Doc doc = adf.parseDoc(json); });
FeatureProvider is a functional interface, so a lambda is sufficient for simple cases. FeatureSet implements FeatureProvider and can be used directly or composed with another provider via featureSet.withOverrides(baseProvider).
The per-call context wrapping shown above is appropriate when only a subset of calls need different feature states. However, in a server application it is often preferable to wire the library to the application's feature flag service once at startup rather than at every call site. Use Context.GlobalDefaultFeatureProvider.set(...) for this:
1 2 3 4 5 6 7 8 9// At application startup — wire the library to your feature flag service Context.GlobalDefaultFeatureProvider.set( feature -> myFlagService.isEnabled(feature.name()) ); // From this point on, every call anywhere in the process benefits from the // registered provider, without any per-call context wrapping. Doc doc = adf.parseDoc(json);
The registered provider acts as the process-wide default. It applies on any thread that has not established a more specific per-thread context. Per-thread overrides via Context.withFeature() or Context.withFeatures() always take precedence over the global default — the two mechanisms compose cleanly.
To restore the built-in defaults, pass null:
1 2Context.GlobalDefaultFeatureProvider.set(null); // restore built-in defaults
Thread safety: GlobalDefaultFeatureProvider.set() updates a volatile field and is safe to call from any thread. However, it affects all threads in the process immediately. It should normally only be called once, at application startup, before any conversions have begun. The expected pattern is to define a @Singleton component that installs itself as the default using @PostConstruct and optionally restores the null value in @PreDestroy.
The Feature context described on this page is only for the core lib module (ADF model, parsing, and validation). The wiki transformer module has a completely separate feature flag mechanism based on FeatureFlag and FeatureFlagProvider objects.
See Conversion Feature Flags for documentation of the wiki module's flags.
Do not confuse the two systems — they are independent and have different APIs.
Rate this page: