java 56 lines · 8 steps

Validating JSON payloads against a schema in Java

A reusable validator loads a JSON Schema once, then reports every violation as a flat list of readable messages.

Explained by highlit
1package com.example.validation;
2 
3import java.util.List;
4import java.util.stream.Collectors;
5 
6import org.everit.json.schema.Schema;
7import org.everit.json.schema.ValidationException;
8import org.everit.json.schema.loader.SchemaLoader;
9import org.json.JSONObject;
10import org.json.JSONTokener;
11 
12public class PayloadValidator {
13 
14 private final Schema schema;
15 
16 public PayloadValidator(String schemaResourcePath) {
17 try (var stream = getClass().getResourceAsStream(schemaResourcePath)) {
18 if (stream == null) {
19 throw new IllegalStateException("Schema not found: " + schemaResourcePath);
20 }
21 JSONObject rawSchema = new JSONObject(new JSONTokener(stream));
22 this.schema = SchemaLoader.builder()
23 .schemaJson(rawSchema)
24 .draftV7Support()
25 .build()
26 .load()
27 .build();
28 } catch (Exception e) {
29 throw new IllegalStateException("Failed to load schema", e);
30 }
31 }
32 
33 public List<String> validate(String json) {
34 JSONObject payload;
35 try {
36 payload = new JSONObject(new JSONTokener(json));
37 } catch (org.json.JSONException e) {
38 return List.of("Malformed JSON: " + e.getMessage());
39 }
40 try {
41 schema.validate(payload);
42 return List.of();
43 } catch (ValidationException e) {
44 return flatten(e);
45 }
46 }
47 
48 private List<String> flatten(ValidationException e) {
49 if (e.getCausingExceptions().isEmpty()) {
50 return List.of(e.getPointerToViolation() + ": " + e.getErrorMessage());
51 }
52 return e.getCausingExceptions().stream()
53 .flatMap(cause -> flatten(cause).stream())
54 .collect(Collectors.toList());
55 }
56}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Compile expensive resources like schemas once in the constructor so each validation call stays cheap.
  2. 2Returning an empty-or-populated list of errors is friendlier to callers than throwing on the first failure.
  3. 3Recursively walking a nested exception's causes turns a tree of violations into one flat, actionable report.

Related explainers

java
public final class NaturalOrderComparator implements Comparator<String> {
 
    public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
 

Natural-order string sorting in Java

comparator natural-sort string-parsing
Intermediate 9 steps
go
package middleware
 
import (
	"net/http"

Localized validation errors in Gin

middleware internationalization validation
Intermediate 8 steps
javascript
async function uploadInBatches(records, uploadFn, { batchSize = 100, concurrency = 3 } = {}) {
  const batches = [];
  for (let i = 0; i < records.length; i += batchSize) {
    batches.push(records.slice(i, i + batchSize));

Uploading records with bounded concurrency

concurrency worker-pool async-await
Advanced 8 steps
python
from flask import Blueprint, jsonify
from marshmallow import Schema, fields, validate, EXCLUDE
from webargs.flaskparser import use_args
 

Validating query params in Flask with webargs

validation schema query-building
Intermediate 10 steps
java
public URI buildSearchUri(String query, int page, int size, List<String> tags) {
    UriComponentsBuilder builder = UriComponentsBuilder
            .fromUriString("https://api.example.com")
            .path("/v2/products/search")

Building URIs safely with UriComponentsBuilder in Spring

url-building builder-pattern encoding
Intermediate 5 steps
go
func (h *ExportHandler) BulkExport(c *gin.Context) {
	projectID := c.Param("projectID")
 
	reports, err := h.reports.ListByProject(c.Request.Context(), projectID)

Streaming a ZIP download in Gin

streaming zip-archive http-headers
Intermediate 8 steps

Share this explainer

Here's the card — post it anywhere.

Validating JSON payloads against a schema in Java — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code