|
| 1 | +--- |
| 2 | +title: Version 5.3 Released! |
| 3 | +date: 2026-03-07 |
| 4 | +author: >- |
| 5 | + [Attila Mészáros](https://github.com/csviri) |
| 6 | +--- |
| 7 | + |
| 8 | +We're pleased to announce the release of Java Operator SDK v5.3.0! This minor version brings |
| 9 | +two headline features — read-cache-after-write consistency and a new metrics implementation — |
| 10 | +along with a configuration adapter system, MDC improvements, and a number of smaller improvements |
| 11 | +and cleanups. |
| 12 | + |
| 13 | +## Key Features |
| 14 | + |
| 15 | +### Read-cache-after-write Consistency and Event Filtering |
| 16 | + |
| 17 | +This is the headline feature of 5.3. Informer caches are inherently eventually consistent: after |
| 18 | +your reconciler updates a resource, there is a window of time before the change is visible in the |
| 19 | +cache. This can cause subtle bugs, particularly when storing allocated values in the status |
| 20 | +sub-resource and reading them back in the next reconciliation. |
| 21 | + |
| 22 | +From 5.3.0, the framework provides two guarantees when you use |
| 23 | +[`ResourceOperations`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java) |
| 24 | +(accessible from `Context`): |
| 25 | + |
| 26 | +1. **Read-after-write**: Reading from the cache after your update — even within the same |
| 27 | + reconciliation — returns at least the version of the resource from your update response. |
| 28 | +2. **Event filtering**: Events produced by your own writes no longer trigger a redundant |
| 29 | + reconciliation. |
| 30 | + |
| 31 | +`UpdateControl` and `ErrorStatusUpdateControl` use this automatically. Secondary resources benefit |
| 32 | +via `context.resourceOperations()`: |
| 33 | + |
| 34 | +```java |
| 35 | +public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) { |
| 36 | + |
| 37 | + ConfigMap managedConfigMap = prepareConfigMap(webPage); |
| 38 | + // update is cached and will suppress the resulting event |
| 39 | + context.resourceOperations().serverSideApply(managedConfigMap); |
| 40 | + |
| 41 | + // fresh resource instantly available from the cache |
| 42 | + var upToDateResource = context.getSecondaryResource(ConfigMap.class); |
| 43 | + |
| 44 | + makeStatusChanges(webPage); |
| 45 | + // UpdateControl also uses this by default |
| 46 | + return UpdateControl.patchStatus(webPage); |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +If your reconciler relied on being re-triggered by its own writes, a new `reschedule()` method on |
| 51 | +`UpdateControl` lets you explicitly request an immediate re-queue. |
| 52 | + |
| 53 | +> **Note**: `InformerEventSource.list(..)` bypasses the additional caches and will not reflect |
| 54 | +> in-flight updates. Use `context.getSecondaryResources(..)` or `InformerEventSource.get(ResourceID)` |
| 55 | +> instead. |
| 56 | +
|
| 57 | +See the [reconciler docs](/docs/documentation/reconciler#read-cache-after-write-consistency-and-event-filtering) |
| 58 | +and the [deep-dive blog post](/blog/news/read-after-write-consistency) for full details. |
| 59 | + |
| 60 | +### MicrometerMetricsV2 |
| 61 | + |
| 62 | +A new micrometer-based `Metrics` implementation designed with low cardinality in mind. All meters |
| 63 | +are scoped to the controller, not to individual resources, avoiding unbounded cardinality growth as |
| 64 | +resources come and go. |
| 65 | + |
| 66 | +```java |
| 67 | +MeterRegistry registry; // initialize your registry |
| 68 | +Metrics metrics = MicrometerMetricsV2.newMicrometerMetricsV2Builder(registry).build(); |
| 69 | +Operator operator = new Operator(client, o -> o.withMetrics(metrics)); |
| 70 | +``` |
| 71 | + |
| 72 | +Optionally attach a `namespace` tag to per-reconciliation counters (disabled by default): |
| 73 | + |
| 74 | +```java |
| 75 | +Metrics metrics = MicrometerMetricsV2.newMicrometerMetricsV2Builder(registry) |
| 76 | + .withNamespaceAsTag() |
| 77 | + .build(); |
| 78 | +``` |
| 79 | + |
| 80 | +The full list of meters: |
| 81 | + |
| 82 | +| Meter | Type | Description | |
| 83 | +|---|---|---| |
| 84 | +| `reconciliations.active` | gauge | Reconciler executions currently running | |
| 85 | +| `reconciliations.queue` | gauge | Resources queued for reconciliation | |
| 86 | +| `custom_resources` | gauge | Resources tracked by the controller | |
| 87 | +| `reconciliations.execution.duration` | timer | Execution duration with explicit histogram buckets | |
| 88 | +| `reconciliations.started.total` | counter | Reconciliations started | |
| 89 | +| `reconciliations.success.total` | counter | Successful reconciliations | |
| 90 | +| `reconciliations.failure.total` | counter | Failed reconciliations | |
| 91 | +| `reconciliations.retries.total` | counter | Retry attempts | |
| 92 | +| `events.received` | counter | Kubernetes events received | |
| 93 | + |
| 94 | +The execution timer uses explicit bucket boundaries (10ms–30s) to ensure compatibility with |
| 95 | +`histogram_quantile()` in both `PrometheusMeterRegistry` and `OtlpMeterRegistry`. |
| 96 | + |
| 97 | +A ready-to-use **Grafana dashboard** is included at |
| 98 | +[`observability/josdk-operator-metrics-dashboard.json`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/observability/josdk-operator-metrics-dashboard.json). |
| 99 | + |
| 100 | +The |
| 101 | +[`metrics-processing` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/metrics-processing) |
| 102 | +provides a complete end-to-end setup with Prometheus, Grafana, and an OpenTelemetry Collector, |
| 103 | +installable via `observability/install-observability.sh`. This is a good starting point for |
| 104 | +verifying metrics in a real cluster. |
| 105 | + |
| 106 | +> **Deprecated**: The original `MicrometerMetrics` (V1) is deprecated as of 5.3.0. It attaches |
| 107 | +> resource-specific metadata as tags to every meter, causing unbounded cardinality. Migrate to |
| 108 | +> `MicrometerMetricsV2`. |
| 109 | +
|
| 110 | +See the [observability docs](/docs/documentation/observability#micrometermetricsv2) for the full |
| 111 | +reference. |
| 112 | + |
| 113 | +### Configuration Adapters |
| 114 | + |
| 115 | +A new `ConfigLoader` bridges any key-value configuration source to the JOSDK operator and |
| 116 | +controller configuration APIs. This lets you drive operator behaviour from environment variables, |
| 117 | +system properties, YAML files, or any config library without writing glue code by hand. |
| 118 | + |
| 119 | +The default instance stacks environment variables over system properties out of the box: |
| 120 | + |
| 121 | +```java |
| 122 | +Operator operator = new Operator(ConfigLoader.getDefault().applyConfigs()); |
| 123 | +``` |
| 124 | + |
| 125 | +Built-in providers: `EnvVarConfigProvider`, `PropertiesConfigProvider`, `YamlConfigProvider`, |
| 126 | +and `AgregatePriorityListConfigProvider` for explicit priority ordering. |
| 127 | + |
| 128 | +`ConfigProvider` is a single-method interface, so adapting any config library (MicroProfile Config, |
| 129 | +SmallRye Config, etc.) takes only a few lines: |
| 130 | + |
| 131 | +```java |
| 132 | +public class SmallRyeConfigProvider implements ConfigProvider { |
| 133 | + private final SmallRyeConfig config; |
| 134 | + |
| 135 | + @Override |
| 136 | + public <T> Optional<T> getValue(String key, Class<T> type) { |
| 137 | + return config.getOptionalValue(key, type); |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +Pass the results when constructing the operator and registering reconcilers: |
| 143 | + |
| 144 | +```java |
| 145 | +var configLoader = new ConfigLoader(new SmallRyeConfigProvider(smallRyeConfig)); |
| 146 | + |
| 147 | +Operator operator = new Operator(configLoader.applyConfigs()); |
| 148 | +operator.register(new MyReconciler(), configLoader.applyControllerConfigs(MyReconciler.NAME)); |
| 149 | +``` |
| 150 | + |
| 151 | +See the [configuration docs](/docs/documentation/configuration#loading-configuration-from-external-sources) |
| 152 | +for the full list of supported keys. |
| 153 | + |
| 154 | +### MDC Improvements |
| 155 | + |
| 156 | +**MDC in workflow execution**: MDC context is now propagated through workflow (dependent resource |
| 157 | +graph) execution threads, not just the top-level reconciler thread. Logging from dependent |
| 158 | +resources now carries the same contextual fields as the primary reconciliation. |
| 159 | + |
| 160 | +**`NO_NAMESPACE` for cluster-scoped resources**: Instead of omitting the `resource.namespace` MDC |
| 161 | +key for cluster-scoped resources, the framework now emits `MDCUtils.NO_NAMESPACE`. This makes log |
| 162 | +queries for cluster-scoped resources reliable. |
| 163 | + |
| 164 | +### De-duplicated Secondary Resources from Context |
| 165 | + |
| 166 | +When multiple event sources manage the same resource type, `context.getSecondaryResources(..)` now |
| 167 | +returns a de-duplicated stream. When the same resource appears from more than one source, only the |
| 168 | +copy with the highest resource version is returned. |
| 169 | + |
| 170 | +### Record Desired State in Context |
| 171 | + |
| 172 | +Dependent resources now record their desired state in the `Context` during reconciliation. This |
| 173 | +allows reconcilers and downstream dependents in a workflow to inspect what a dependent resource |
| 174 | +computed as its desired state, which is useful for debugging and composite workflows. |
| 175 | + |
| 176 | +## Additional Improvements |
| 177 | + |
| 178 | +- **Annotation removal using locking**: Finalizer and annotation management no longer uses |
| 179 | + `createOrReplace`; a locking-based `createOrUpdate` avoids conflicts under concurrent updates. |
| 180 | +- **`KubernetesDependentResource` uses `ResourceOperations` directly**, removing an indirection |
| 181 | + layer and automatically benefiting from the read-after-write guarantees. |
| 182 | +- **Skip namespace deletion in JUnit extension**: The JUnit extension now supports a flag to skip |
| 183 | + namespace deletion after a test run, useful for debugging CI failures. |
| 184 | +- **`ManagedInformerEventSource.getCachedValue()` deprecated**: Use |
| 185 | + `context.getSecondaryResource(..)` instead. |
| 186 | +- **Improved event filtering for multiple parallel updates**: The filtering algorithm now handles |
| 187 | + cases where multiple parallel updates are in flight for the same resource. |
| 188 | +- `exitOnStopLeading` is being prepared for removal from the public API. |
| 189 | + |
| 190 | +## Migration Notes |
| 191 | + |
| 192 | +### JUnit module rename |
| 193 | + |
| 194 | +```xml |
| 195 | +<!-- before --> |
| 196 | +<artifactId>operator-framework-junit-5</artifactId> |
| 197 | +<!-- after --> |
| 198 | +<artifactId>operator-framework-junit</artifactId> |
| 199 | +``` |
| 200 | + |
| 201 | +### `Metrics` interface renames |
| 202 | + |
| 203 | +| v5.2 | v5.3 | |
| 204 | +|---|---| |
| 205 | +| `reconcileCustomResource` | `reconciliationSubmitted` | |
| 206 | +| `reconciliationExecutionStarted` | `reconciliationStarted` | |
| 207 | +| `reconciliationExecutionFinished` | `reconciliationSucceeded` | |
| 208 | +| `failedReconciliation` | `reconciliationFailed` | |
| 209 | +| `finishedReconciliation` | `reconciliationFinished` | |
| 210 | +| `cleanupDoneFor` | `cleanupDone` | |
| 211 | +| `receivedEvent` | `eventReceived` | |
| 212 | + |
| 213 | +`reconciliationFinished(..)` is extended with `RetryInfo`. `monitorSizeOf(..)` is removed. |
| 214 | + |
| 215 | +See the full [migration guide](/docs/migration/v5-3-migration) for details. |
| 216 | + |
| 217 | +## Getting Started |
| 218 | + |
| 219 | +```xml |
| 220 | +<dependency> |
| 221 | + <groupId>io.javaoperatorsdk</groupId> |
| 222 | + <artifactId>operator-framework</artifactId> |
| 223 | + <version>5.3.0</version> |
| 224 | +</dependency> |
| 225 | +``` |
| 226 | + |
| 227 | +## All Changes |
| 228 | + |
| 229 | +See the [comparison view](https://github.com/operator-framework/java-operator-sdk/compare/v5.2.0...v5.3.0) |
| 230 | +for the full list of changes. |
| 231 | + |
| 232 | +## Feedback |
| 233 | + |
| 234 | +Please report issues or suggest improvements on our |
| 235 | +[GitHub repository](https://github.com/operator-framework/java-operator-sdk/issues). |
| 236 | + |
| 237 | +Happy operator building! 🚀 |
0 commit comments