java 36 lines · 8 steps

Batch JSON Merge Patch in Spring

A Spring REST endpoint applies RFC 7396 JSON merge patches to many products in one request.

Explained by highlit
1@RestController
2@RequestMapping("/api/products")
3@RequiredArgsConstructor
4public class ProductBatchController {
5 
6 private final ProductRepository productRepository;
7 private final ObjectMapper objectMapper;
8 
9 @PatchMapping(path = "/batch", consumes = "application/merge-patch+json")
10 public ResponseEntity<List<ProductDto>> patchBatch(@RequestBody List<BatchPatch> patches) {
11 List<ProductDto> updated = new ArrayList<>(patches.size());
12 
13 for (BatchPatch patch : patches) {
14 Product product = productRepository.findById(patch.id())
15 .orElseThrow(() -> new ProductNotFoundException(patch.id()));
16 
17 Product merged = applyMergePatch(patch.patch(), product);
18 updated.add(ProductDto.from(productRepository.save(merged)));
19 }
20 
21 return ResponseEntity.ok(updated);
22 }
23 
24 private Product applyMergePatch(JsonNode patch, Product target) {
25 try {
26 ObjectNode current = objectMapper.valueToTree(target);
27 JsonNode result = JsonMergePatch.fromJson(patch).apply(current);
28 return objectMapper.treeToValue(result, Product.class);
29 } catch (JsonPatchException | JsonProcessingException e) {
30 throw new InvalidPatchException("Unable to apply merge patch for product " + target.getId(), e);
31 }
32 }
33 
34 public record BatchPatch(@NotNull Long id, @NotNull JsonNode patch) {
35 }
36}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1JSON Merge Patch lets clients send only the fields they want changed, and the server merges them onto the existing entity.
  2. 2Serializing an entity to a JsonNode, applying the patch, then deserializing back gives a clean way to do partial updates without manual field mapping.
  3. 3Wrapping library-specific exceptions in a domain exception keeps patch failures meaningful to API callers.

Related explainers

java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
java
public static Map<String, String> parseCookieHeader(String header) {
    Map<String, String> cookies = new LinkedHashMap<>();
    if (header == null || header.isBlank()) {
        return cookies;

Parsing an HTTP Cookie header in Java

string-parsing http url-decoding
Intermediate 6 steps
java
public class TimedSocketReader {
 
    private static final int READ_TIMEOUT_MS = 5_000;
    private static final int CONNECT_TIMEOUT_MS = 3_000;

Reading a socket with connect and read timeouts

sockets timeouts io
Intermediate 8 steps
java
public final class EncodingDetector {
 
    public enum Encoding {
        UTF_8, UTF_16LE, UTF_16BE, UTF_32LE, UTF_32BE, ASCII, UNKNOWN

Detecting text encoding from raw bytes in Java

byte-manipulation encoding-detection bitwise-operations
Intermediate 8 steps
java
public final class ImportOrganizer {
 
    private static final Pattern IMPORT_LINE =
            Pattern.compile("^import\\s+(static\\s+)?([\\w.]+(?:\\.\\*)?)\\s*;\\s*$");

Sorting Java imports with a regex pass

regex sorting text-processing
Intermediate 8 steps

Share this explainer

Here's the card — post it anywhere.

Batch JSON Merge Patch in Spring — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code