|
| 1 | +--- |
| 2 | +title: Version 5.2 Released! |
| 3 | +date: 2025-11-25 |
| 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.2! This minor version brings several powerful new features |
| 9 | +and improvements that enhance the framework's capabilities for building Kubernetes operators. This release focuses on |
| 10 | +flexibility, external resource management, and advanced reconciliation patterns. |
| 11 | + |
| 12 | +## Key Features |
| 13 | + |
| 14 | +### ResourceIDMapper for External Resources |
| 15 | + |
| 16 | +One of the most significant improvements in 5.2 is the introduction of a unified approach to working with custom ID types |
| 17 | +across the framework through [`ResourceIDMapper`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDMapper.java) |
| 18 | +and [`ResourceIDProvider`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDProvider.java). |
| 19 | + |
| 20 | +Previously, when working with external resources (non-Kubernetes resources), the framework assumed resource IDs could always |
| 21 | +be represented as strings. This limitation made it challenging to work with external systems that use complex ID types. |
| 22 | + |
| 23 | +Now, you can define custom ID types for your external resources by implementing the `ResourceIDProvider` interface: |
| 24 | + |
| 25 | +```java |
| 26 | +public class MyExternalResource implements ResourceIDProvider<MyCustomID> { |
| 27 | + @Override |
| 28 | + public MyCustomID getResourceID() { |
| 29 | + return new MyCustomID(this.id); |
| 30 | + } |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +This capability is integrated across multiple components: |
| 35 | +- `ExternalResourceCachingEventSource` |
| 36 | +- `ExternalBulkDependentResource` |
| 37 | +- `AbstractExternalDependentResource` and its subclasses |
| 38 | + |
| 39 | +If you cannot modify the external resource class (e.g., it's generated or final), you can provide a custom |
| 40 | +`ResourceIDMapper` to the components above. |
| 41 | + |
| 42 | +See the [migration guide](/docs/migration/v5-2-migration) for detailed migration instructions. |
| 43 | + |
| 44 | +### Trigger Reconciliation on All Events |
| 45 | + |
| 46 | +Version 5.2 introduces a new execution mode that provides finer control over when reconciliation occurs. By setting |
| 47 | +[`triggerReconcilerOnAllEvent`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java) |
| 48 | +to `true`, your `reconcile` method will be called for **every** event, including `Delete` events. |
| 49 | + |
| 50 | +This is particularly useful when: |
| 51 | +- Only some primary resources need finalizers (e.g., some resources create external resources, others don't) |
| 52 | +- You maintain custom in-memory caches that need cleanup without using finalizers |
| 53 | +- You need fine-grained control over resource lifecycle |
| 54 | + |
| 55 | +When enabled: |
| 56 | +- The `reconcile` method receives the last known state even if the resource is deleted |
| 57 | +- Check deletion status using `Context.isPrimaryResourceDeleted()` |
| 58 | +- Retry, rate limiting, and rescheduling work normally |
| 59 | +- You manage finalizers explicitly using `PrimaryUpdateAndCacheUtils` |
| 60 | + |
| 61 | +Example: |
| 62 | + |
| 63 | +```java |
| 64 | +@ControllerConfiguration(triggerReconcilerOnAllEvent = true) |
| 65 | +public class MyReconciler implements Reconciler<MyResource> { |
| 66 | + |
| 67 | + @Override |
| 68 | + public UpdateControl<MyResource> reconcile(MyResource resource, Context<MyResource> context) { |
| 69 | + if (context.isPrimaryResourceDeleted()) { |
| 70 | + // Handle deletion |
| 71 | + cleanupCache(resource); |
| 72 | + return UpdateControl.noUpdate(); |
| 73 | + } |
| 74 | + // Normal reconciliation |
| 75 | + return UpdateControl.patchStatus(resource); |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +See the detailed [documentation](/docs/documentation/reconciler#trigger-reconciliation-for-all-events) and |
| 81 | +[integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/triggerallevent/finalizerhandling). |
| 82 | + |
| 83 | +### Expectation Pattern Support (Experimental) |
| 84 | + |
| 85 | +The framework now provides built-in support for the [expectations pattern](https://ahmet.im/blog/controller-pitfalls/#expectations-pattern), |
| 86 | +a common Kubernetes controller design pattern that ensures secondary resources are in an expected state before proceeding. |
| 87 | + |
| 88 | +The expectation pattern helps avoid race conditions and ensures your controller makes decisions based on the most current |
| 89 | +state of your resources. The implementation is available in the |
| 90 | +[`io.javaoperatorsdk.operator.processing.expectation`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/) |
| 91 | +package. |
| 92 | + |
| 93 | +Example usage: |
| 94 | + |
| 95 | +```java |
| 96 | +public class MyReconciler implements Reconciler<MyResource> { |
| 97 | + |
| 98 | + private final ExpectationManager<MyResource> expectationManager = new ExpectationManager<>(); |
| 99 | + |
| 100 | + @Override |
| 101 | + public UpdateControl<MyResource> reconcile(MyResource primary, Context<MyResource> context) { |
| 102 | + |
| 103 | + // Exit early if expectation is not yet fulfilled or timed out |
| 104 | + if (expectationManager.ongoingExpectationPresent(primary, context)) { |
| 105 | + return UpdateControl.noUpdate(); |
| 106 | + } |
| 107 | + |
| 108 | + var deployment = context.getSecondaryResource(Deployment.class); |
| 109 | + if (deployment.isEmpty()) { |
| 110 | + createDeployment(primary, context); |
| 111 | + expectationManager.setExpectation( |
| 112 | + primary, Duration.ofSeconds(30), deploymentReadyExpectation(context)); |
| 113 | + return UpdateControl.noUpdate(); |
| 114 | + } |
| 115 | + |
| 116 | + // Check if expectation is fulfilled |
| 117 | + var result = expectationManager.checkExpectation("deploymentReady", primary, context); |
| 118 | + if (result.isFulfilled()) { |
| 119 | + return updateStatusReady(primary); |
| 120 | + } else if (result.isTimedOut()) { |
| 121 | + return updateStatusTimeout(primary); |
| 122 | + } |
| 123 | + |
| 124 | + return UpdateControl.noUpdate(); |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +This feature is marked as `@Experimental` as we gather feedback and may refine the API based on user experience. Future |
| 130 | +versions may integrate this pattern directly into Dependent Resources and Workflows. |
| 131 | + |
| 132 | +See the [documentation](/docs/documentation/reconciler#expectations) and |
| 133 | +[integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/ExpectationReconciler.java). |
| 134 | + |
| 135 | +### Field Selectors for InformerEventSource |
| 136 | + |
| 137 | +You can now use field selectors when configuring `InformerEventSource`, allowing you to filter resources at the server |
| 138 | +side before they're cached locally. This reduces memory usage and network traffic by only watching resources that match |
| 139 | +your criteria. |
| 140 | + |
| 141 | +Field selectors work similarly to label selectors but filter on resource fields like `metadata.name` or `status.phase`: |
| 142 | + |
| 143 | +```java |
| 144 | +@Informer( |
| 145 | + fieldSelector = @FieldSelector( |
| 146 | + fields = @Field(key = "status.phase", value = "Running") |
| 147 | + ) |
| 148 | +) |
| 149 | +``` |
| 150 | + |
| 151 | +This is particularly useful when: |
| 152 | +- You only care about resources in specific states |
| 153 | +- You want to reduce the memory footprint of your operator |
| 154 | +- You're watching cluster-scoped resources and only need a subset |
| 155 | + |
| 156 | +See the [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/fieldselector/FieldSelectorIT.java) |
| 157 | +for examples. |
| 158 | + |
| 159 | +### AggregatedMetrics for Multiple Metrics Providers |
| 160 | + |
| 161 | +The new `AggregatedMetrics` class implements the composite pattern, allowing you to combine multiple metrics |
| 162 | +implementations. This is useful when you need to send metrics to different monitoring systems simultaneously. |
| 163 | + |
| 164 | +```java |
| 165 | +// Create individual metrics instances |
| 166 | +Metrics micrometerMetrics = MicrometerMetrics.withoutPerResourceMetrics(registry); |
| 167 | +Metrics customMetrics = new MyCustomMetrics(); |
| 168 | +Metrics loggingMetrics = new LoggingMetrics(); |
| 169 | + |
| 170 | +// Combine them into a single aggregated instance |
| 171 | +Metrics aggregatedMetrics = new AggregatedMetrics(List.of( |
| 172 | + micrometerMetrics, |
| 173 | + customMetrics, |
| 174 | + loggingMetrics |
| 175 | +)); |
| 176 | + |
| 177 | +// Use with your operator |
| 178 | +Operator operator = new Operator(client, o -> o.withMetrics(aggregatedMetrics)); |
| 179 | +``` |
| 180 | + |
| 181 | +This enables hybrid monitoring strategies, such as sending metrics to both Prometheus and a custom logging system. |
| 182 | + |
| 183 | +See the [observability documentation](/docs/documentation/observability#aggregated-metrics) for more details. |
| 184 | + |
| 185 | +## Additional Improvements |
| 186 | + |
| 187 | +### GenericRetry Enhancements |
| 188 | + |
| 189 | +- `GenericRetry` no longer provides a mutable singleton instance, improving thread safety |
| 190 | +- Configurable duration for initial retry interval |
| 191 | + |
| 192 | +### Test Infrastructure Improvements |
| 193 | + |
| 194 | +- Ability to override test infrastructure Kubernetes client separately, providing more flexibility in testing scenarios |
| 195 | + |
| 196 | +### Fabric8 Client Update |
| 197 | + |
| 198 | +Updated to Fabric8 Kubernetes Client 7.4.0, bringing the latest features and bug fixes from the client library. |
| 199 | + |
| 200 | +## Experimental Annotations |
| 201 | + |
| 202 | +Starting with this release, new features marked as experimental will be annotated with `@Experimental`. This annotation |
| 203 | +indicates that while we intend to support the feature, the API may evolve based on user feedback. |
| 204 | + |
| 205 | +## Migration Notes |
| 206 | + |
| 207 | +For most users, upgrading to 5.2 should be straightforward. The main breaking change involves the introduction of |
| 208 | +`ResourceIDMapper` for external resources. If you're using external dependent resources or bulk dependents with custom |
| 209 | +ID types, please refer to the [migration guide](/docs/migration/v5-2-migration). |
| 210 | + |
| 211 | +## Getting Started |
| 212 | + |
| 213 | +Update your dependency to version 5.2.0: |
| 214 | + |
| 215 | +```xml |
| 216 | +<dependency> |
| 217 | + <groupId>io.javaoperatorsdk</groupId> |
| 218 | + <artifactId>operator-framework</artifactId> |
| 219 | + <version>5.2.0</version> |
| 220 | +</dependency> |
| 221 | +``` |
| 222 | + |
| 223 | +## All Changes |
| 224 | + |
| 225 | +You can see all changes in the [comparison view](https://github.com/operator-framework/java-operator-sdk/compare/v5.1.5...v5.2.0). |
| 226 | + |
| 227 | +## Feedback |
| 228 | + |
| 229 | +As always, we welcome your feedback! Please report issues or suggest improvements on our |
| 230 | +[GitHub repository](https://github.com/operator-framework/java-operator-sdk/issues). |
| 231 | + |
| 232 | +Happy operator building! 🚀 |
0 commit comments