Title: jsonMergePatch operations use Fabric8 JSON Patch semantics and can remove spec · Issue #3512 · operator-framework/java-operator-sdk · GitHub
Open Graph Title: jsonMergePatch operations use Fabric8 JSON Patch semantics and can remove spec · Issue #3512 · operator-framework/java-operator-sdk
X Title: jsonMergePatch operations use Fabric8 JSON Patch semantics and can remove spec · Issue #3512 · operator-framework/java-operator-sdk
Description: What happened? ResourceOperations.jsonMergePatch(...) and ResourceOperations.jsonMergePatchPrimary(...) are documented as applying RFC 7386 JSON Merge Patch, but they delegate to Fabric8's no-context .patch() method. Fabric8's default .p...
Open Graph Description: What happened? ResourceOperations.jsonMergePatch(...) and ResourceOperations.jsonMergePatchPrimary(...) are documented as applying RFC 7386 JSON Merge Patch, but they delegate to Fabric8's no-conte...
X Description: What happened? ResourceOperations.jsonMergePatch(...) and ResourceOperations.jsonMergePatchPrimary(...) are documented as applying RFC 7386 JSON Merge Patch, but they delegate to Fabric8's no-c...
Opengraph URL: https://github.com/operator-framework/java-operator-sdk/issues/3512
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"jsonMergePatch operations use Fabric8 JSON Patch semantics and can remove spec","articleBody":"### What happened?\n\n`ResourceOperations.jsonMergePatch(...)` and `ResourceOperations.jsonMergePatchPrimary(...)` are documented as applying RFC 7386 JSON Merge Patch, but they delegate to Fabric8's no-context `.patch()` method.\n\nFabric8's default `.patch()` intentionally fetches the current resource and computes an RFC 6902 JSON Patch diff. When the supplied resource is a partial metadata patch, the generated diff removes fields that are omitted from the partial object, including `spec`.\n\nThe status variants are not affected because Fabric8's `patchStatus()` explicitly selects `PatchType.JSON_MERGE`.\n\n### Versions verified\n\n- Java Operator SDK 5.3.5 with Fabric8 7.7.0\n- Java Operator SDK 5.5.0 with Fabric8 7.8.0\n\nThe behavior is still present in the current `main` branch.\n\n### Minimal reproduction\n\nAssume this custom resource already exists:\n\n```yaml\napiVersion: example.io/v1\nkind: Example\nmetadata:\n name: example\nspec:\n value: keep-me\n```\n\nApply a partial metadata patch through `ResourceOperations`:\n\n```java\nExample patch = new Example();\npatch.getMetadata().setName(\"example\");\npatch.getMetadata().setLabels(Map.of(\"resolved\", \"true\"));\n\ncontext.resourceOperations().jsonMergePatchPrimary(patch);\n```\n\nFabric8 computes a JSON Patch equivalent to:\n\n```json\n[\n {\"op\":\"add\",\"path\":\"/metadata/labels/resolved\",\"value\":\"true\"},\n {\"op\":\"remove\",\"path\":\"/spec\"},\n {\"op\":\"remove\",\"path\":\"/status\"}\n]\n```\n\nFor a CRD whose root `spec` is optional, the API server accepts the patch and permanently removes the spec. We encountered this in a real controller where subsequent reconciliation failed because `getSpec()` returned null.\n\n### Expected behavior\n\nMethods named and documented as JSON Merge Patch should send `application/merge-patch+json` and preserve fields omitted from the partial resource.\n\n### Relevant implementation\n\nJOSDK 5.5.0 delegates to `.patch()` without specifying a patch type:\n\n- [`ResourceOperations.jsonMergePatchPrimary`](https://github.com/operator-framework/java-operator-sdk/blob/v5.5.0/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java#L838-L843)\n\nFabric8's default `.patch()` selects JSON Patch and diffs the current and supplied resources:\n\n- [`HasMetadataOperation.patch()`](https://github.com/fabric8io/kubernetes-client/blob/v7.8.0/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/HasMetadataOperation.java#L245-L246)\n- [`OperationSupport.handlePatch(...)`](https://github.com/fabric8io/kubernetes-client/blob/v7.8.0/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationSupport.java#L377-L397)\n\n### Suggested fix\n\nMake the non-status JSON Merge Patch operations pass an explicit patch context, for example:\n\n```java\nr -> context.getClient()\n .resource(r)\n .patch(PatchContext.of(PatchType.JSON_MERGE), r)\n```\n\nThe cache and own-event filtering behavior can remain unchanged. Regression coverage should create a complete resource, apply a partial metadata merge patch, and assert that `spec` survives.\n","author":{"url":"https://github.com/brianzilliz","@type":"Person","name":"brianzilliz"},"datePublished":"2026-07-23T08:01:42Z","url":"https://github.com/operator-framework/java-operator-sdk/issues/3512"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:c3e48303-c6de-01c1-504f-6bbee62a487d |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 8BAE:137C1B:F589AF:1421E6E:6AED5DD9 |
| html-safe-nonce | cf5f354a9a22bbf16a52bb34fe7aedcf67dd43d42b15660369f3701d416ed653 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4QkFFOjEzN0MxQjpGNTg5QUY6MTQyMUU2RTo2QUVENUREOSIsInZpc2l0b3JfaWQiOiIyMjM1NzUyNTg1MDA1NzE5MDAxIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | b67c655d322688fe6c647aeba2a0f39a531104fcddf9dd204f16e3fd9404e411 |
| hovercard-subject-tag | issue:4956755580 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/operator-framework/java-operator-sdk/3512/issue_layout |
| twitter:image | https://opengraph.githubassets.com/e83ea2bfeee12244da65671987405f29625abb2810f05edb894f2f75c371504e/operator-framework/java-operator-sdk/issues/3512 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/e83ea2bfeee12244da65671987405f29625abb2810f05edb894f2f75c371504e/operator-framework/java-operator-sdk/issues/3512 |
| og:image:alt | What happened? ResourceOperations.jsonMergePatch(...) and ResourceOperations.jsonMergePatchPrimary(...) are documented as applying RFC 7386 JSON Merge Patch, but they delegate to Fabric8's no-conte... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | brianzilliz |
| hostname | github.com |
| expected-hostname | github.com |
| None | 56fb42677ae87ed3e64410323d67980aa65d0485568a7858f874e7edfc6d1c11 |
| turbo-cache-control | no-preview |
| go-import | github.com/operator-framework/java-operator-sdk git https://github.com/operator-framework/java-operator-sdk.git |
| octolytics-dimension-user_id | 38202270 |
| octolytics-dimension-user_login | operator-framework |
| octolytics-dimension-repository_id | 188592933 |
| octolytics-dimension-repository_nwo | operator-framework/java-operator-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 188592933 |
| octolytics-dimension-repository_network_root_nwo | operator-framework/java-operator-sdk |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c9a37e2423ed382cf39f70b5aa4b4858b40a3b0 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width