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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1JSON Merge Patch lets clients send only the fields they want changed, and the server merges them onto the existing entity.
- 2Serializing an entity to a JsonNode, applying the patch, then deserializing back gives a clean way to do partial updates without manual field mapping.
- 3Wrapping library-specific exceptions in a domain exception keeps patch failures meaningful to API callers.
Related explainers
java
@Component public class RegionCacheWarmer implements SmartInitializingSingleton { private static final Logger log = LoggerFactory.getLogger(RegionCacheWarmer.class);
Warming a Spring cache at startup
caching
startup-hook
dependency-injection
Intermediate
7 steps
java
public final class EmailNormalizer { private static final Pattern EMAIL_PATTERN = Pattern.compile( "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
Normalizing email addresses in Java
validation
regex
normalization
Intermediate
8 steps
typescript
import { Controller, All, Req,
Catch-all routes and error shaping in NestJS
exception-handling
routing
middleware
Intermediate
6 steps
typescript
import { ArgumentsHost, Catch, ConflictException,
Turning TypeORM lock errors into 409s in NestJS
exception-handling
optimistic-locking
http-status
Intermediate
6 steps
java
public class TimedFetchService { private final ExecutorService executor = Executors.newFixedThreadPool(8); private final HttpClient httpClient = HttpClient.newHttpClient();
Enforcing HTTP timeouts with a Future
concurrency
timeouts
thread-pool
Intermediate
8 steps
java
@Component public class RefreshTokenSuccessHandler implements AuthenticationSuccessHandler { private final RefreshTokenService refreshTokenService;
Issuing JWT and refresh tokens on login in Spring
authentication
jwt
http-cookies
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/batch-json-merge-patch-in-spring-explained-java-fec9/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.