|
| 1 | +--- |
| 2 | +title: Version 5.3 Released! |
| 3 | +date: 2026-03-13 |
| 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) for details. |
| 58 | + |
| 59 | +### MicrometerMetricsV2 |
| 60 | + |
| 61 | +A new micrometer-based `Metrics` implementation designed with low cardinality in mind. All meters |
| 62 | +are scoped to the controller, not to individual resources, avoiding unbounded cardinality growth as |
| 63 | +resources come and go. |
| 64 | + |
| 65 | +```java |
| 66 | +MeterRegistry registry; // initialize your registry |
| 67 | +Metrics metrics = MicrometerMetricsV2.newBuilder(registry).build(); |
| 68 | +Operator operator = new Operator(client, o -> o.withMetrics(metrics)); |
| 69 | +``` |
| 70 | + |
| 71 | +Optionally attach a `namespace` tag to per-reconciliation counters (disabled by default): |
| 72 | + |
| 73 | +```java |
| 74 | +Metrics metrics = MicrometerMetricsV2.newBuilder(registry) |
| 75 | + .withNamespaceAsTag() |
| 76 | + .build(); |
| 77 | +``` |
| 78 | + |
| 79 | +The full list of meters: |
| 80 | + |
| 81 | +| Meter | Type | Description | |
| 82 | +|---|---|---| |
| 83 | +| `reconciliations.active` | gauge | Reconciler executions currently running | |
| 84 | +| `reconciliations.queue` | gauge | Resources queued for reconciliation | |
| 85 | +| `custom_resources` | gauge | Resources tracked by the controller | |
| 86 | +| `reconciliations.execution.duration` | timer | Execution duration with explicit histogram buckets | |
| 87 | +| `reconciliations.started.total` | counter | Reconciliations started | |
| 88 | +| `reconciliations.success.total` | counter | Successful reconciliations | |
| 89 | +| `reconciliations.failure.total` | counter | Failed reconciliations | |
| 90 | +| `reconciliations.retries.total` | counter | Retry attempts | |
| 91 | +| `events.received` | counter | Kubernetes events received | |
| 92 | + |
| 93 | +The execution timer uses explicit bucket boundaries (10ms–30s) to ensure compatibility with |
| 94 | +`histogram_quantile()` in both `PrometheusMeterRegistry` and `OtlpMeterRegistry`. |
| 95 | + |
| 96 | +A ready-to-use **Grafana dashboard** is included at |
| 97 | +[`observability/josdk-operator-metrics-dashboard.json`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/observability/josdk-operator-metrics-dashboard.json). |
| 98 | + |
| 99 | +The |
| 100 | +[`metrics-processing` sample operator](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/sample-operators/metrics-processing) |
| 101 | +provides a complete end-to-end setup with Prometheus, Grafana, and an OpenTelemetry Collector, |
| 102 | +installable via `observability/install-observability.sh`. This is a good starting point for |
| 103 | +verifying metrics in a real cluster. |
| 104 | + |
| 105 | +> **Deprecated**: The original `MicrometerMetrics` (V1) is deprecated as of 5.3.0. It attaches |
| 106 | +> resource-specific metadata as tags to every meter, causing unbounded cardinality. Migrate to |
| 107 | +> `MicrometerMetricsV2`. |
| 108 | +
|
| 109 | +See the [observability docs](/docs/documentation/observability#micrometermetricsv2) for the full |
| 110 | +reference. |
| 111 | + |
| 112 | +### Configuration Adapters |
| 113 | + |
| 114 | +A new `ConfigLoader` bridges any key-value configuration source to the JOSDK operator and |
| 115 | +controller configuration APIs. This lets you drive operator behaviour from environment variables, |
| 116 | +system properties, YAML files, or any config library without writing glue code by hand. |
| 117 | + |
| 118 | +The default instance stacks environment variables over system properties out of the box: |
| 119 | + |
| 120 | +```java |
| 121 | +Operator operator = new Operator(ConfigLoader.getDefault().applyConfigs()); |
| 122 | +``` |
| 123 | + |
| 124 | +Built-in providers: `EnvVarConfigProvider`, `PropertiesConfigProvider`, `YamlConfigProvider`, |
| 125 | +and `AggregatePriorityListConfigProvider` for explicit priority ordering. |
| 126 | + |
| 127 | +`ConfigProvider` is a single-method interface, so adapting any config library (MicroProfile Config, |
| 128 | +SmallRye Config, etc.) takes only a few lines: |
| 129 | + |
| 130 | +```java |
| 131 | +public class SmallRyeConfigProvider implements ConfigProvider { |
| 132 | + private final SmallRyeConfig config; |
| 133 | + |
| 134 | + @Override |
| 135 | + public <T> Optional<T> getValue(String key, Class<T> type) { |
| 136 | + return config.getOptionalValue(key, type); |
| 137 | + } |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +Pass the results when constructing the operator and registering reconcilers: |
| 142 | + |
| 143 | +```java |
| 144 | +var configLoader = new ConfigLoader(new SmallRyeConfigProvider(smallRyeConfig)); |
| 145 | + |
| 146 | +Operator operator = new Operator(configLoader.applyConfigs()); |
| 147 | +operator.register(new MyReconciler(), configLoader.applyControllerConfigs(MyReconciler.NAME)); |
| 148 | +``` |
| 149 | + |
| 150 | +See the [configuration docs](/docs/documentation/configuration#loading-configuration-from-external-sources) |
| 151 | +for the full list of supported keys. |
| 152 | + |
| 153 | +> **Note**: This new configuration mechanism is useful when using the SDK by itself. Framework (Spring Boot, Quarkus, …) |
| 154 | +> integrations usually provide their own configuration mechanisms that should be used instead of this new mechanism. |
| 155 | +
|
| 156 | +### MDC Improvements |
| 157 | + |
| 158 | +**MDC in workflow execution**: MDC context is now propagated through workflow (dependent resource |
| 159 | +graph) execution threads, not just the top-level reconciler thread. Logging from dependent |
| 160 | +resources now carries the same contextual fields as the primary reconciliation. |
| 161 | + |
| 162 | +**`NO_NAMESPACE` for cluster-scoped resources**: Instead of omitting the `resource.namespace` MDC |
| 163 | +key for cluster-scoped resources, the framework now emits `MDCUtils.NO_NAMESPACE`. This makes log |
| 164 | +queries for cluster-scoped resources reliable. |
| 165 | + |
| 166 | +### De-duplicated Secondary Resources from Context |
| 167 | + |
| 168 | +When multiple event sources manage the same resource type, `context.getSecondaryResources(..)` now |
| 169 | +returns a de-duplicated stream. When the same resource appears from more than one source, only the |
| 170 | +copy with the highest resource version is returned. |
| 171 | + |
| 172 | +### Record Desired State in Context |
| 173 | + |
| 174 | +Dependent resources now record their desired state in the `Context` during reconciliation. This allows reconcilers and |
| 175 | +downstream dependents in a workflow to inspect what a dependent resource computed as its desired state and guarantees |
| 176 | +that the desired state is computed only once per reconciliation. |
| 177 | + |
| 178 | +### Informer Health Checks |
| 179 | + |
| 180 | +Informer health checks no longer rely on `isWatching`. For readiness and startup probes, you should |
| 181 | +primarily use `hasSynced`. Once an informer has started, `isWatching` is not suitable for liveness |
| 182 | +checks. |
| 183 | + |
| 184 | +## Additional Improvements |
| 185 | + |
| 186 | +- **Annotation removal using locking**: Finalizer and annotation management no longer uses |
| 187 | + `createOrReplace`; a locking-based `createOrUpdate` avoids conflicts under concurrent updates. |
| 188 | +- **`KubernetesDependentResource` uses `ResourceOperations` directly**, removing an indirection |
| 189 | + layer and automatically benefiting from the read-after-write guarantees. |
| 190 | +- **Skip namespace deletion in JUnit extension**: The JUnit extension now supports a flag to skip |
| 191 | + namespace deletion after a test run, useful for debugging CI failures. |
| 192 | +- **`ManagedInformerEventSource.getCachedValue()` deprecated**: Use |
| 193 | + `context.getSecondaryResource(..)` instead. |
| 194 | +- **Improved event filtering for multiple parallel updates**: The filtering algorithm now handles |
| 195 | + cases where multiple parallel updates are in flight for the same resource. |
| 196 | +- `exitOnStopLeading` is being prepared for removal from the public API. |
| 197 | + |
| 198 | +## Migration Notes |
| 199 | + |
| 200 | +### JUnit module rename |
| 201 | + |
| 202 | +```xml |
| 203 | +<!-- before --> |
| 204 | +<artifactId>operator-framework-junit-5</artifactId> |
| 205 | +<!-- after --> |
| 206 | +<artifactId>operator-framework-junit</artifactId> |
| 207 | +``` |
| 208 | + |
| 209 | +### `Metrics` interface renames |
| 210 | + |
| 211 | +| v5.2 | v5.3 | |
| 212 | +|---|---| |
| 213 | +| `reconcileCustomResource` | `reconciliationSubmitted` | |
| 214 | +| `reconciliationExecutionStarted` | `reconciliationStarted` | |
| 215 | +| `reconciliationExecutionFinished` | `reconciliationSucceeded` | |
| 216 | +| `failedReconciliation` | `reconciliationFailed` | |
| 217 | +| `finishedReconciliation` | `reconciliationFinished` | |
| 218 | +| `cleanupDoneFor` | `cleanupDone` | |
| 219 | +| `receivedEvent` | `eventReceived` | |
| 220 | + |
| 221 | +`reconciliationFinished(..)` is extended with `RetryInfo`. `monitorSizeOf(..)` is removed. |
| 222 | + |
| 223 | +See the full [migration guide](/docs/migration/v5-3-migration) for details. |
| 224 | + |
| 225 | +## Getting Started |
| 226 | + |
| 227 | +```xml |
| 228 | +<dependency> |
| 229 | + <groupId>io.javaoperatorsdk</groupId> |
| 230 | + <artifactId>operator-framework</artifactId> |
| 231 | + <version>5.3.0</version> |
| 232 | +</dependency> |
| 233 | +``` |
| 234 | + |
| 235 | +## All Changes |
| 236 | + |
| 237 | +See the [comparison view](https://github.com/operator-framework/java-operator-sdk/compare/v5.2.0...v5.3.0) |
| 238 | +for the full list of changes. |
| 239 | + |
| 240 | +## Feedback |
| 241 | + |
| 242 | +Please report issues or suggest improvements on our |
| 243 | +[GitHub repository](https://github.com/operator-framework/java-operator-sdk/issues). |
| 244 | + |
| 245 | +Happy operator building! 🚀 |
0 commit comments