DEV Community

Yu Han
Yu Han

Posted on

2x Faster JSON Schema Validation in Java (Without JsonNode)

Most JSON Schema libraries in Java follow the same pattern:

  • Validate a JSON string
  • Or validate a JsonNode
  • Or validate some library-specific tree model

Which makes sense — JSON Schema was designed for JSON documents.

But in real applications, we rarely live in JSON strings.
We live in Java objects.

So the usual flow looks like this:

POJO → serialize → JsonNode → validate
Enter fullscreen mode Exit fullscreen mode

It works. It’s common.
But it’s not the only possible model.

A Slightly Different Mental Model

Instead of saying:

“JSON Schema validates JSON.”

We can say:

“JSON Schema validates structured data.”

That includes:

  • POJOs / JOJOs
  • Map / List
  • primitive values
  • any Java objects.

SJF4J treats these as first-class schema nodes via an Object-Based Node Tree model.
There is no mandatory JsonNode layer in between.

So this:

class Order {
    public int id;
    public String user;
}
Enter fullscreen mode Exit fullscreen mode

Can be validated directly:

JsonSchema schema = JsonSchema.fromJson("""
{
  "type": "object",
  "required": ["id"],
  "properties": {
    "id": { "type": "integer" },
    "user": { "format": "email" }
  }
}
""");

schema.compile();

Order order = new Order();
order.id = 1;
order.user = "alice@example.com";

boolean valid = schema.isValid(order);
Enter fullscreen mode Exit fullscreen mode

No serialization.
No JsonNode.
No adapter layer.

Just validate the object.


JSON Schema + JSR 380: Not Competitors

JSR 380 (Bean Validation) is excellent for core invariants:

class User {
    @NotNull
    @Email
    String email;
}
Enter fullscreen mode Exit fullscreen mode

But it’s:

  • Annotation-driven
  • Static
  • Bound to compiled code

JSON Schema handles a different layer:

  • Runtime-configurable validation
  • Conditional logic (if/then/else)
  • Structural policies
  • Externalized contracts

With SJF4J, you can layer JSON Schema directly onto your domain model:

@ValidJsonSchema("""
{
  "type": "object",
  "required": ["id"],
  "properties": {
    "id": { "type": "integer" },
    "user": { "format": "email" }
  }
}
""")
public class Order {
    public int id;
    public String user;
}
Enter fullscreen mode Exit fullscreen mode

And validate it without converting anything:

SchemaValidator validator = new SchemaValidator();
ValidationResult result = validator.validate(new Order());
Enter fullscreen mode Exit fullscreen mode

In practice, the split becomes very clean:

  • JSR 380 → domain invariants
  • JSON Schema → runtime contracts & policies

They complement each other instead of overlapping.


Performance

In local bowtie runs against the official JSON Schema test suite:

  • SJF4J performs roughly 2× faster than networknt’s validator
  • Currently among the fastest Java JSON Schema validators in bowtie benchmarks

By validating native objects directly, SJF4J avoids:

  • Serialization
  • Re-parsing
  • Intermediate tree construction

The simpler model isn’t slower.
In many cases, it’s measurably faster.


A Small Architectural Shift

Most JSON tooling in Java assumes:

JSON is primary. Objects are derived.

This flips that:

Structured data is primary. JSON is just a representation.

Once validation operates directly on native objects, a surprising amount of glue code disappears.

Not dramatically.
Not heroically.
Just quietly.

And after writing more adapter layers than I care to admit, I’ve learned to appreciate quiet improvements.


If you’re curious:

https://github.com/sjf4j-projects/sjf4j

Top comments (0)