Tired of validating data with JSON Schema? We built an alternative

java dev.to

We've been there. JSON Schema gets hard to write as soon as your payload is non-trivial. Conditional logic, cross-field rules, business invariants, and at some point we stop writing contracts at all. We go code-first, generate the schema from annotations, and end up with 200 lines very few understand, and error messages referencing paths like #/properties/items/allOf/0/then/Then that map to nothing in our mental model.

So we built Okyline. A contract is just an annotated JSON example: you describe your data the way you'd explain it to a colleague. The engine derives all validation automatically.

Here's what progressive adoption looks like in practice, using an e-commerce order as a running example.


Step 1 - Your JSON is already a contract

No syntax to learn. Wrap your payload in $oky and you have a contract:

{"$oky":{"orderId":"ORD-20250107","customer":{"id":42,"email":"alice@example.com"},"items":[{"sku":"PRD-00123","quantity":2,"unitPrice":79.99,"lineTotal":159.98}],"total":170.36}}
Enter fullscreen mode Exit fullscreen mode

That's it. The engine infers types from the values, id is an integer, email is a string, unitPrice is a number, items is an array of objects with a specific shape.

What this already gives you: structure validation and type checking on every payload. No missing fields slipping through as null, no string arriving where you expected a number. A whole class of bugs, gone, for zero effort.


Step 2 - Add constraints one field at a time

Annotations go on the field name, not in a separate block. The value stays as the example.

{"$oky":{"orderId|@ ~$OrderId~|Order identifier":"ORD-20250107","customer|@":{"id|@ # (>0)":42,"email|@ ~$Email~":"alice@example.com"},"items|@ [1,50] -> !":[{"sku|@ # ~$Sku~":"PRD-00123","quantity|@ (1..999)":2,"unitPrice|@ (>0)":79.99,"lineTotal|(>0)":159.98}],"total|@ (>0)":170.36},"$format":{"OrderId":"^ORD-[0-9]{8}$","Sku":"^[A-Z]{3}-[0-9]{5}$"}}
Enter fullscreen mode Exit fullscreen mode

Reading each field left to right:

orderId|@ ~$OrderId~|Order identifier, required (@), must match the named pattern $OrderId defined in the $format section at the bottom of the contract, labelled "Order identifier".

items|@ [1,50] -> !, required (@), an array of objects, between 1 and 50 items. The -> operator introduces constraints on the elements of the array — here ! means each element must be unique by key fields (#).

That last part matters more than it looks. JSON Schema has uniqueItems: true but it compares whole objects. Two lines with the same SKU but different quantities? JSON Schema considers them unique and lets them through. Your stock gets decremented twice, your invoice is wrong, and no schema violation was reported. With Okyline, same SKU twice → validation error, regardless of the other fields.

quantity|@ (1..999), required, integer between 1 and 999. One line.

The contract still reads like the original payload. A business analyst can follow it. A developer can modify it without reading documentation first.

The core syntax fits on one screen. 9 symbols cover most use cases. The language goes much further when you need it.


Step 3 - Conditional logic that stays readable

Real payloads have variants. A PREMIUM customer has loyalty points and a discount rate. A BUSINESS customer has a company name and a VAT number. In JSON Schema, this means nested allOf/if/then/else chains. In Okyline, it's $appliedIf:

{"$oky":{"orderId|@ ~$OrderId~|Order identifier":"ORD-20250107","customer|@":{"id|@ # (>0)":42,"email|@ ~$Email~":"alice@example.com","type|@ ($CUSTOMER_TYPE)":"PREMIUM","$appliedIf type":{"('PREMIUM')":{"loyaltyPoints|@ (>=0)":1500,"discountRate|@ (0..30)":15},"('BUSINESS')":{"companyName|@ {2,100}":"Acme Corp","vatNumber|@ ~$VatNumber~":"FR12345678901"}}},"items|@ [1,50] -> !":[{"sku|@ # ~$Sku~":"PRD-00123","quantity|@ (1..999)":2,"unitPrice|@ (>0)":79.99,"lineTotal|(>0)":159.98}],"total|@ (>0)":170.36},"$format":{"OrderId":"^ORD-[0-9]{8}$","Sku":"^[A-Z]{3}-[0-9]{5}$","VatNumber":"^[A-Z]{2}[0-9]{9,12}$"},"$nomenclature":{"CUSTOMER_TYPE":"STANDARD, PREMIUM, BUSINESS"}}
Enter fullscreen mode Exit fullscreen mode

Two customer types, two branches. You can follow the logic without a whiteboard. Nomenclatures are defined once in $nomenclature and referenced everywhere. When the list of valid customer types changes, you change it in one place.


Step 4 - Business invariants that JSON Schema simply cannot express

This is where JSON Schema stops entirely.

lineTotal must equal quantity * unitPrice. total must equal the sum of all lineTotal across the items array. These are business rules every e-commerce system has. JSON Schema cannot express either of them. You end up with post-validation code, or nothing, and a class of bugs that slips through to production.

In Okyline, two lines in $compute. No new fields, no change to the payload structure:

{"$oky":{"orderId|@ ~$OrderId~|Order identifier":"ORD-20250107","customer|@":{"id|@ # (>0)":42,"email|@ ~$Email~":"alice@example.com","type|@ ($CUSTOMER_TYPE)":"PREMIUM","$appliedIf type":{"('PREMIUM')":{"loyaltyPoints|@ (>=0)":1500,"discountRate|@ (0..30)":15},"('BUSINESS')":{"companyName|@ {2,100}":"Acme Corp","vatNumber|@ ~$VatNumber~":"FR12345678901"}}},"items|@ [1,50] -> !":[{"sku|@ # ~$Sku~":"PRD-00123","quantity|@ (1..999)":2,"unitPrice|@ (>0)":79.99,"lineTotal|@ (%LineTotal)":159.98}],"total|@ (%Total)":170.36},"$format":{"OrderId":"^ORD-[0-9]{8}$","Sku":"^[A-Z]{3}-[0-9]{5}$","VatNumber":"^[A-Z]{2}[0-9]{9,12}$"},"$nomenclature":{"CUSTOMER_TYPE":"STANDARD, PREMIUM, BUSINESS"},"$compute":{"LineTotal":"lineTotal == quantity * unitPrice","Total":"total == sum(items, lineTotal)"}}
Enter fullscreen mode Exit fullscreen mode

The engine validates types, constraints, conditions, and computed invariants, all from the same contract. Nothing to wire up separately.

What does this look like in JSON Schema?

Here's the equivalent contract in JSON Schema (draft-07).
It's 143 lines. Scroll through it, you don't need to read it.

You don't have to write this by hand neither. The Okyline Studio Free generates the equivalent JSON Schema automatically from your contract — that's where the 143 lines above come from. Edit a field on the left, watch the JSON Schema update in real time on the right.

{"$schema":"http://json-schema.org/draft-07/schema","x-oky-generated-from":"okyline","type":"object","properties":{"orderId":{"type":"string","title":"Order identifier","examples":["ORD-20250107"],"pattern":"^ORD-[0-9]{8}$"},"customer":{"type":"object","properties":{"id":{"type":"integer","examples":[42],"exclusiveMinimum":0,"x-oky-keyField":true},"email":{"type":"string","examples":["alice@example.com"],"format":"email"},"type":{"type":"string","examples":["PREMIUM"],"enum":["STANDARD","PREMIUM","BUSINESS"]}},"required":["id","email","type"],"if":{"properties":{"type":{"const":"PREMIUM"}},"required":["type"]},"then":{"properties":{"loyaltyPoints":{"type":"integer","examples":[1500],"minimum":0},"discountRate":{"type":"integer","examples":[15],"minimum":0,"maximum":30}},"required":["loyaltyPoints","discountRate"]},"else":{"if":{"properties":{"type":{"const":"BUSINESS"}},"required":["type"]},"then":{"properties":{"companyName":{"type":"string","examples":["Acme Corp"],"minLength":2,"maxLength":100},"vatNumber":{"type":"string","examples":["FR12345678901"],"pattern":"^[A-Z]{2}[0-9]{9,12}$"}},"required":["companyName","vatNumber"]}}},"items":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","properties":{"sku":{"type":"string","examples":["PRD-00123"],"pattern":"^[A-Z]{3}-[0-9]{5}$","x-oky-keyField":true},"quantity":{"type":"integer","examples":[2],"minimum":1,"maximum":999},"unitPrice":{"type":"number","examples":[79.99],"exclusiveMinimum":0},"lineTotal":{"type":"number","examples":[159.98],"x-oky-compute":"LineTotal"}},"required":["sku","quantity","unitPrice","lineTotal"],"additionalProperties":false},"x-oky-uniqueByKeyFields":true},"total":{"type":"number","examples":[170.36],"x-oky-compute":"Total"}},"required":["orderId","customer","items","total"],"additionalProperties":false,"x-oky-compute-expressions":{"LineTotal":"lineTotal == quantity * unitPrice","Total":"total == sum(items, lineTotal)"}}
Enter fullscreen mode Exit fullscreen mode

Done scrolling? Notice the x-oky- fields, x-oky-compute, x-oky-keyField, x-oky-uniqueByKeyFields. Those are Okyline extensions. A standard JSON Schema validator silently ignores them.
The key-field uniqueness, the computed business rules, the cross-field invariants, none of it actually executes. The Okyline contract above is 41 lines and everything executes.


5 errors in the payload: invalid email, missing conditional field, wrong line total, duplicate SKU, wrong order total. All caught, all explained in plain language.

👉 Try it in the browser, community.studio.okyline.io


Running it in Java

The library is free for any use — development, production, commercial products, no limits. No license key, no registration, no usage cap. The license is perpetual and irrevocable for any version you adopt. Zero transitive dependencies. Under 800 KB. Thread-safe. Your schemas remain your intellectual property. Available on Maven Central.

The Okyline language specification itself is open, published under CC BY-SA 4.0. Anyone can implement it independently. You're adopting a format, not just an implementation.

<dependency>
  <groupId>io.akwatype</groupId>
  <artifactId>okyline</artifactId>
  <version>1.6.1</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode
import io.akwatype.okyline.api.OkylineApi;
import java.util.List;

OkylineApi okyline = new OkylineApi();

// Load and compile the contract once
List<String> schemaErrors = okyline.loadSchema(schema);
if (!schemaErrors.isEmpty()) {
    schemaErrors.forEach(System.err::println);
    return;
}

// Validate any payload
List<String> errors = okyline.validate(json);
if (errors.isEmpty()) {
    System.out.println("Valid ✓");
} else {
    errors.forEach(System.err::println);
}
Enter fullscreen mode Exit fullscreen mode

Errors are precise and actionable:

The string 'alice@exa@mple.com' at 'customer.email' does not match okyline built-in validation '$Email'
Field 'customer.loyaltyPoints' must be present due to APPLIED_IF on 'type'
Value: 80.01 at: 'items[1].lineTotal' does not match (%LineTotal) : '80.01 == 80' failed
Object item at 'items[1]' has a duplicate key: PRD-00123
Value: 239.98 at: 'total' does not match (%Total) : '239.98 == 239.99' failed
Enter fullscreen mode Exit fullscreen mode

Where to go from here

The e-commerce example covers Steps 1 through 4. For more complex payloads, IoT sensor streams, booking systems, financial transactions, the gap with JSON Schema widens further. The Studio ships with more than 10 built-in examples ranging from simple to complex, all editable and runnable in the browser.

Okyline is designed for progressive adoption. You stop where your use case stops. Step 1 alone is already useful. Step 4 covers what JSON Schema cannot reach.

👉 Try it in the browser, community.studio.okyline.io

No backend. No signup. Paste your contract, validate your payload, see errors in real time.

👉 Full documentation and open specification, community.okyline.design-hub.okyline.io


Built by Akwatype. Questions or feedback welcome.

Source: dev.to

arrow_back Back to Tutorials