diff --git a/examples/examples-release-latest/README.md b/examples/examples-release-latest/README.md
index 80c34ea272..12312d8be3 100644
--- a/examples/examples-release-latest/README.md
+++ b/examples/examples-release-latest/README.md
@@ -11,3 +11,23 @@ mvn compile
mvn exec:java -Dexec.mainClass="io.kubernetes.client.examples.Example"
```
+## Patching custom objects
+
+Kubernetes PATCH requests require a patch-specific content type. Use `PatchUtils` rather than
+calling a generated custom-object patch request's `execute()` method directly.
+
+The
+[`PatchCustomObjectExample`](src/main/java/io/kubernetes/client/examples/PatchCustomObjectExample.java)
+shows how to patch a namespaced custom object with JSON Patch:
+
+```sh
+cat > /tmp/widget-patch.json <<'EOF'
+[
+ {"op": "replace", "path": "/spec/size", "value": 3}
+]
+EOF
+
+mvn exec:java \
+ -Dexec.mainClass="io.kubernetes.client.examples.PatchCustomObjectExample" \
+ -Dexec.args="example.com v1 default widgets my-widget /tmp/widget-patch.json"
+```
diff --git a/examples/examples-release-latest/src/main/java/io/kubernetes/client/examples/PatchCustomObjectExample.java b/examples/examples-release-latest/src/main/java/io/kubernetes/client/examples/PatchCustomObjectExample.java
new file mode 100644
index 0000000000..2487bc063a
--- /dev/null
+++ b/examples/examples-release-latest/src/main/java/io/kubernetes/client/examples/PatchCustomObjectExample.java
@@ -0,0 +1,71 @@
+/*
+Copyright 2026 The Kubernetes Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package io.kubernetes.client.examples;
+
+import io.kubernetes.client.custom.V1Patch;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CustomObjectsApi;
+import io.kubernetes.client.util.Config;
+import io.kubernetes.client.util.PatchUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Demonstrates JSON-patching a namespaced custom object.
+ *
+ *
The generated patch API defaults to {@code application/json}. Kubernetes patch requests must
+ * instead use a patch-specific media type, which {@link PatchUtils} supplies.
+ *
+ *
Run with arguments {@code }. For
+ * example:
+ *
+ * {@code
+ * mvn exec:java \
+ * -Dexec.mainClass="io.kubernetes.client.examples.PatchCustomObjectExample" \
+ * -Dexec.args="example.com v1 default widgets my-widget /tmp/widget-patch.json"
+ * }
+ */
+public final class PatchCustomObjectExample {
+
+ private PatchCustomObjectExample() {}
+
+ public static void main(String[] args) throws IOException, ApiException {
+ if (args.length != 6) {
+ throw new IllegalArgumentException(
+ "Expected arguments: ");
+ }
+
+ ApiClient client = Config.defaultClient();
+ CustomObjectsApi api = new CustomObjectsApi(client);
+ String patchJson = Files.readString(Path.of(args[5]));
+
+ Object result =
+ PatchUtils.patch(
+ Object.class,
+ () ->
+ api.patchNamespacedCustomObject(
+ args[0],
+ args[1],
+ args[2],
+ args[3],
+ args[4],
+ new V1Patch(patchJson))
+ .buildCall(null),
+ V1Patch.PATCH_FORMAT_JSON_PATCH,
+ client);
+
+ System.out.println(result);
+ }
+}
diff --git a/util/src/test/java/io/kubernetes/client/util/PatchUtilsTest.java b/util/src/test/java/io/kubernetes/client/util/PatchUtilsTest.java
index 36937fee1d..3bebcfdd22 100644
--- a/util/src/test/java/io/kubernetes/client/util/PatchUtilsTest.java
+++ b/util/src/test/java/io/kubernetes/client/util/PatchUtilsTest.java
@@ -14,16 +14,23 @@
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.apis.CustomObjectsApi;
import io.kubernetes.client.openapi.models.V1Pod;
+import java.util.Map;
+import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
class PatchUtilsTest {
@@ -106,4 +113,76 @@ void strategicMergePatchPod() throws ApiException {
apiServer.verify(1, patchRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/pods/foo")));
}
+
+ private static Stream customObjectPatchFormats() {
+ return Stream.of(
+ Arguments.of(
+ V1Patch.PATCH_FORMAT_JSON_PATCH,
+ "[{\"op\":\"replace\",\"path\":\"/spec/size\",\"value\":3}]"),
+ Arguments.of(
+ V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH, "{\"spec\":{\"size\":3}}"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("customObjectPatchFormats")
+ void patchNamespacedCustomObject(String patchFormat, String patchBody) throws ApiException {
+ CustomObjectsApi customObjectsApi = new CustomObjectsApi(client);
+ String resourcePath = "/apis/example.com/v1/namespaces/default/widgets/foo";
+ apiServer.stubFor(
+ patch(urlPathEqualTo(resourcePath))
+ .withHeader("Content-Type", containing(patchFormat))
+ .withRequestBody(equalToJson(patchBody))
+ .willReturn(
+ aResponse()
+ .withStatus(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody("{\"metadata\":{\"name\":\"foo\"}}")));
+
+ Object result =
+ PatchUtils.patch(
+ Object.class,
+ () ->
+ customObjectsApi
+ .patchNamespacedCustomObject(
+ "example.com",
+ "v1",
+ "default",
+ "widgets",
+ "foo",
+ new V1Patch(patchBody))
+ .buildCall(null),
+ patchFormat,
+ client);
+
+ assertEquals("foo", ((Map, ?>) ((Map, ?>) result).get("metadata")).get("name"));
+ apiServer.verify(1, patchRequestedFor(urlPathEqualTo(resourcePath)));
+ }
+
+ @Test
+ void jsonPatchClusterCustomObject() throws ApiException {
+ CustomObjectsApi customObjectsApi = new CustomObjectsApi(client);
+ String patchBody = "[{\"op\":\"replace\",\"path\":\"/spec/size\",\"value\":3}]";
+ String resourcePath = "/apis/example.com/v1/widgets/foo";
+ apiServer.stubFor(
+ patch(urlPathEqualTo(resourcePath))
+ .withHeader("Content-Type", containing(V1Patch.PATCH_FORMAT_JSON_PATCH))
+ .withRequestBody(equalToJson(patchBody))
+ .willReturn(
+ aResponse()
+ .withStatus(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody("{}")));
+
+ PatchUtils.patch(
+ Object.class,
+ () ->
+ customObjectsApi
+ .patchClusterCustomObject(
+ "example.com", "v1", "widgets", "foo", new V1Patch(patchBody))
+ .buildCall(null),
+ V1Patch.PATCH_FORMAT_JSON_PATCH,
+ client);
+
+ apiServer.verify(1, patchRequestedFor(urlPathEqualTo(resourcePath)));
+ }
}