Version 5.5 Released!
We’re pleased to announce the release of Java Operator SDK v5.5.0! This minor version reworks how
the framework filters the events caused by a controller’s own writes, making it more correct and
more efficient, and exposes a richer, matcher-aware ResourceOperations API. There are no
breaking API changes, but there is one behavioral change around UpdateControl — see the
migration notes.
If you are running on version 5.3.x or 5.4.x upgrade is strongly recommend!
Key Features
Matcher-based updates in ResourceOperations
ResourceOperations (available from the reconciliation Context via context.resourceOperations())
now offers a complete, consistent family of update/patch/create methods for every write strategy —
server-side apply, update (PUT), JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386) — each
available for the whole resource and the status subresource, and with dedicated primary
variants.
Every method comes in two flavors: a default one, and one taking an Options argument that controls
how the resulting own event is handled:
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
makeStatusChanges(webPage);
// updates the status, filters the own event, and skips the write entirely if nothing changed
context.resourceOperations().serverSideApplyPrimaryStatus(webPage);
return UpdateControl.noUpdate();
}
By default these operations match the desired state against the actual (cached) state before
writing: if they already match, the write is skipped; otherwise the write is performed and its own
event is filtered. This is the most efficient option — it covers full event filtering and avoids a
request to the Kubernetes API server when nothing changed. Default matchers are provided for every
operation type; when one does not fit, supply your own via Options.matchAndFilter(matcher).
Options exposes the available strategies:
matchAndFilter(...)/matchAndFilterWithDefaultMatcher(...)— match, then write-and-filter only if needed. This is the default for the server-side apply and patch methods.filterWithOptimisticLocking()— filter the own event; the write must use optimistic locking, otherwise anIllegalArgumentExceptionis thrown. The matcher /updateTypeoverloads also skip the write when the desired state already matches; this match + optimistic locking combination is the default for the PUTupdatemethods.cacheOnly()— only cache the response (read-cache-after-write consistency), no filtering.forceFilterEvents()— always filter (mostly internal usage). Assumes resource was matched before or update is done using optimistic locking.
Correctness note: filtering an own event is only safe if the framework can tell an own write apart from a concurrent third-party write. This requires either a matcher or optimistic locking; otherwise a concurrent external update inside the filter window may be missed until the next resync.
More correct own-event filtering (no-op update edge case)
The read-cache-after-write own-event filter has been reworked to fix an edge case where a legitimate external change could be filtered out together with a controller’s own no-op update — for example, when the spec was changed externally while the controller patched its status and that status patch turned out to be a no-op. The new matcher-based operations avoid issuing such no-op writes in the first place, and the filtering itself is now correct in these concurrent scenarios.
Additional Improvements
GenericKubernetesResourceMatcher.matchStatus(...): a status-only counterpart tomatch(...)that compares just the/statussubtree.- New
MatcherSPI:io.javaoperatorsdk.operator.api.reconciler.matcher.Matcherlets you plug a custom matching strategy intoOptions.matchAndFilter(...). - Quieter logs for reconciler-handled errors: when
updateErrorStatus(...)returns anyErrorStatusUpdateControlother thandefaultErrorProcessing(), the error is treated as handled by the reconciler and logged atDEBUGinstead of theUncaught error during event processingWARN. Native retry (including@GradualRetryexponential backoff) is unchanged, so a reconciler can keep retrying a recoverable condition without spamming warnings. ReturndefaultErrorProcessing()to keep the previous behavior. - Startup-latency metric: a new
Metrics.eventProcessingStarted(Controller)callback fires when a controller’s event processor begins accepting events (including after a deferred start such as winning leader election). The Micrometer implementations record a per-controller gauge with the JVM uptime at that moment, making operator startup latency measurable. - Extensive integration tests covering every
ResourceOperationsupdate/patch operation (primary, status, and secondary resources) against a real cluster.
Migration Notes
There are no breaking API changes; existing code compiles and runs unchanged. However:
UpdateControl no longer filters own events by default
Returning an UpdateControl (or ErrorStatusUpdateControl) still updates the resource and keeps the
cache read-after-write consistent, but it no longer filters the resulting own event by default —
so the write may cause an additional reconciliation (which should be idempotent). This change fixes correctness edge
cases where an event that should have propagated was previously swallowed.
If you relied on the previous filtering, perform the update through ResourceOperations and return
UpdateControl.noUpdate():
// before (v5.4)
resource.setStatus(new MyStatus().setReady(true));
return UpdateControl.patchStatus(resource);
// after (v5.5) — filter the own event explicitly
resource.setStatus(new MyStatus().setReady(true));
context.resourceOperations().serverSideApplyPrimaryStatus(resource);
return UpdateControl.noUpdate();
See the migration guide for details.
Getting Started
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework</artifactId>
<version>5.5.0</version>
</dependency>
All Changes
See the comparison view for the full list of changes.
Feedback
Please report issues or suggest improvements on our GitHub repository.
Happy operator building! 🚀