Guide

JSON Patch in Java

How to apply and generate RFC 6902 JSON Patches in Java with the java-json-tools library on top of Jackson - with working code you can copy straight into a project.

Installing the library

The reference implementation is java-json-tools/json-patch, built on Jackson. It implements every RFC 6902 operation - add, remove, replace, move, copy and test:

<!-- Maven -->
<dependency>
    <groupId>com.github.java-json-tools</groupId>
    <artifactId>json-patch</artifactId>
    <version>1.13</version>
</dependency>

// Gradle
implementation 'com.github.java-json-tools:json-patch:1.13'

Applying a JSON Patch

Parse both the document and the patch with Jackson's ObjectMapper, build a JsonPatch, and apply it. The result is a new JsonNode:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.JsonPatch;

ObjectMapper mapper = new ObjectMapper();

JsonNode doc = mapper.readTree(
    "{\"name\": \"demo\", \"version\": 1, \"tags\": [\"a\", \"b\"]}"
);

JsonNode patchJson = mapper.readTree(
    "[{\"op\": \"replace\", \"path\": \"/version\", \"value\": 2},"
  + " {\"op\": \"add\", \"path\": \"/tags/-\", \"value\": \"c\"},"
  + " {\"op\": \"remove\", \"path\": \"/name\"}]"
);

JsonPatch patch = JsonPatch.fromJson(patchJson);
JsonNode result = patch.apply(doc);

System.out.println(mapper.writeValueAsString(result));
// {"tags":["a","b","c"],"version":2}

Note the special path /tags/-: the dash appends to the end of an array. Paths follow JSON Pointer (RFC 6901), so ~ and / inside keys are escaped as ~0 and ~1.

Generating a patch from two documents

JsonDiff.asJsonPatch diffs two JsonNode documents and returns the RFC 6902 operations as a JSON array - the same thing the JSON Patch Generator does in your browser:

import com.github.fge.jsonpatch.diff.JsonDiff;

JsonNode original = mapper.readTree("{\"name\": \"demo\", \"version\": 1}");
JsonNode modified = mapper.readTree(
    "{\"name\": \"demo\", \"version\": 2, \"tags\": [\"a\"]}"
);

JsonNode patchJson = JsonDiff.asJsonPatch(original, modified);

System.out.println(mapper.writeValueAsString(patchJson));
// [{"op":"replace","path":"/version","value":2},{"op":"add","path":"/tags","value":["a"]}]

Handling test failures and errors

Two different things can go wrong. JsonPatch.fromJson throws JsonPatchException when the patch document itself is invalid - unknown operation, missing path, malformed JSON Pointer. patch.apply throws JsonPatchException when a well-formed patch does not fit the document - a missing path, an array index out of range, or a test operation whose value does not match:

try {
    JsonPatch patch = JsonPatch.fromJson(patchJson);
    JsonNode result = patch.apply(doc);
    return result;
} catch (JsonPatchException e) {
    // malformed patch OR rejected by the document
    // (a failed test operation lands here - HTTP 412 territory)
    throw new IllegalArgumentException("JSON Patch failed: " + e.getMessage(), e);
}

RFC 6902 stops evaluation at the first failed operation. Rollback of earlier mutations is implementation-specific, so verify the library before treating a patch as a transaction.

Common pitfalls

  • Evaluation stops on error. RFC 6902 does not guarantee rollback of earlier operations; handle exceptions and verify this library's behavior before assuming transaction-like semantics.
  • Numbers are not strings. A test on {"value": 1} fails against {"value": "1"}. Type mismatches are the most common source of failed test operations, and Jackson makes them easy to miss: keep your JsonNode types (IntNode, DoubleNode, TextNode) consistent between patch and document.
  • Array indices shift. Removing index 0 makes index 1 become index 0. Order remove operations from the highest index down, or use value-based test guards.
  • JsonNode is immutable. apply returns a new tree; assign the result. To map it back to a POJO, use mapper.treeToValue(result, MyClass.class).

Try it in the browser first

Before writing Java, prototype the patch visually. Paste your two documents into the JSON Patch Generator to get the exact RFC 6902 operations, or use the JSON Diff tool to see every changed line. Then drop the generated patch into patch.apply.

New to the format? Read the JSON Patch documentation or browse real-world examples.