Java implementation of the Blue language core: https://github.com/bluecontract/blue-spec
Blue is a deterministic document language for describing data, types, and identity. A Blue document can be parsed, resolved against its type graph, reduced to canonical content, and addressed by a stable content hash called a BlueId. Blue Contracts and Processor 1.0 is implemented as a separate runtime target on top of the language layer for document processing, channels, handlers, events, gas, checkpoints, embedded scopes, lifecycle, and termination.
This library gives Java applications the foundations needed to work with Blue:
- parse and serialize Blue YAML/JSON;
- compute deterministic BlueIds;
- resolve
typechains and{ blueId: ... }references; - validate deterministic
schemaconstraints; - support list control forms such as
$previous,$pos, and$empty; - build immutable
FrozenNodeandResolvedSnapshotruntime views; - match nodes against type/shape patterns efficiently;
- apply canonical patches;
- run the generic snapshot-backed document processor;
- register custom channel, handler, and marker processors.
Blue Language 1.0 and Blue Contracts and Processor 1.0 have separate conformance suites and reports.
Gradle:
repositories {
mavenCentral()
}
dependencies {
implementation "blue.language:blue-language-java:3.0.0"
}Maven:
<dependency>
<groupId>blue.language</groupId>
<artifactId>blue-language-java</artifactId>
<version>3.0.0</version>
</dependency>A Blue document is a tree of nodes. A node has one payload kind:
- scalar value;
- list items;
- object fields.
Nodes can also carry language metadata such as name, description, type,
schema, itemType, keyType, valueType, and blueId.
name: Counter
description: Small document with one integer field
counter:
type: Integer
value: 0The Java representation is blue.language.model.Node. It is mutable and useful
for parsing, authoring, serialization, and compatibility APIs.
In Blue, a type is also a Blue node. A document with type inherits and must
conform to that type.
name: Price
amount:
type: Integer
currency:
type: TextAn instance can point to the type by BlueId:
type:
blueId: <PriceBlueId>
amount: 150
currency: EURResolving the instance makes inherited fields, type metadata, and constraints available in the runtime view.
A BlueId is a deterministic content address. It is calculated from canonical Blue content using RFC 8785-style canonical JSON input and SHA-256/Base58 output.
In canonical Blue, { blueId: X } is a pure reference. It cannot be mixed with
sibling content:
# valid
type:
blueId: 4th6...
# invalid
type:
blueId: 4th6...
name: PriceThis keeps reference identity unambiguous.
Blue distinguishes two useful views:
- canonical content: minimized content used for identity and storage;
- resolved content: runtime view with inherited type state available.
ResolvedSnapshot contains both views as immutable FrozenNode graphs:
ResolvedSnapshot
canonicalRoot -> minimized identity source
resolvedRoot -> runtime view
blueId -> canonicalRoot.blueId()
Use snapshots for hot processing paths. Use mutable Node values at the edges
where you parse, serialize, or build documents programmatically.
import blue.language.Blue;
import blue.language.model.Node;
Blue blue = new Blue();
Node node = blue.yamlToNode(
"name: Counter\n" +
"counter: 0\n");
String json = blue.nodeToJson(node);
String yaml = blue.nodeToYaml(node);
System.out.println(json);
System.out.println(yaml);Use calculateBlueId when the node itself is the content you want to address.
String blueId = blue.calculateBlueId(node);
System.out.println(blueId);Structural BlueIds are sensitive to authored content. If a document contains a redundant inherited override, that override is part of the structural input.
Use calculateSemanticBlueId when you want identity after preprocess, resolve,
and minimization.
String semanticBlueId = blue.calculateSemanticBlueId(node);
System.out.println(semanticBlueId);Semantic identity is useful when different authored forms should be treated as the same document because they resolve to the same minimized meaning.
Blue resolves { blueId: ... } references through a NodeProvider.
For tests and local tools, BasicNodeProvider is often enough:
import blue.language.Blue;
import blue.language.model.Node;
import blue.language.provider.BasicNodeProvider;
Blue bootstrap = new Blue();
Node priceType = bootstrap.yamlToNode(
"name: Price\n" +
"amount:\n" +
" type: Integer\n" +
"currency:\n" +
" type: Text\n");
BasicNodeProvider provider = new BasicNodeProvider(priceType);
String priceTypeBlueId = provider.getBlueIdByName("Price");
Blue blue = new Blue(provider);
Node price = blue.yamlToNode(
"type:\n" +
" blueId: " + priceTypeBlueId + "\n" +
"amount: 150\n" +
"currency: EUR\n");
Node resolved = blue.resolve(price);
System.out.println(blue.nodeToYaml(resolved));For production storage, implement NodeProvider:
import blue.language.NodeProvider;
import blue.language.model.Node;
import java.util.Collections;
import java.util.List;
public final class DatabaseNodeProvider implements NodeProvider {
private final BlueDocumentStore store;
public DatabaseNodeProvider(BlueDocumentStore store) {
this.store = store;
}
@Override
public List<Node> fetchByBlueId(String blueId) {
Node content = store.fetchCanonicalNode(blueId);
return content == null ? Collections.emptyList() : Collections.singletonList(content);
}
}The provider should return canonical Blue content for a requested BlueId. The library wraps providers internally to support single-document and multi-document reference forms.
schema provides deterministic core validation. Supported keywords include:
requiredminLengthmaxLengthminimummaximumexclusiveMinimumexclusiveMaximummultipleOfminItemsmaxItemsuniqueItemsminFieldsmaxFieldsenum
Example:
name: Product
sku:
type: Text
schema:
required: true
minLength: 3
maxLength: 32
quantity:
type: Integer
schema:
minimum: 0Blue list resolution supports overlays over inherited lists.
type:
blueId: <BaseListType>
items:
- $previous:
blueId: <InheritedItemsBlueId>
- $pos: 1
value: replacement
- value: appended$previous anchors the inherited list. $pos replaces a specific inherited
position. Normal items after the overlay append to the result.
items:
- $empty: true$empty: true is content. It is not the same as an absent list.
type: List
mergePolicy: append-only
items:
- value: firstSupported list policies:
positionalappend-only
The resolver, minimizer, and BlueId calculator all understand these list-control forms.
ResolvedSnapshot is the preferred runtime representation.
import blue.language.snapshot.ResolvedSnapshot;
ResolvedSnapshot snapshot = blue.resolveToSnapshot(price);
System.out.println(snapshot.blueId());
System.out.println(snapshot.frozenCanonicalRoot().blueId());
System.out.println(snapshot.frozenResolvedRoot().blueId());Snapshots provide:
- immutable canonical root;
- immutable resolved root;
- cached per-node BlueIds;
- path indexes for fast reads;
- structural sharing for resolved references and type graphs.
Read a node by JSON Pointer:
import blue.language.snapshot.FrozenNode;
FrozenNode amount = snapshot.resolvedAt("/amount");
System.out.println(amount.getValue());Use JSON Pointer escaping for literal / and ~ in field names:
FrozenNode value = snapshot.resolvedAt("/a~1b/c~0d");This addresses the object path:
a/b:
c~d: valueBlue keeps a resolved snapshot cache and a resolved reference cache.
ResolvedSnapshot first = blue.resolveToSnapshot(price);
ResolvedSnapshot second = blue.loadSnapshot(first.blueId());
System.out.println(first == second); // true when loaded from the in-memory cache
System.out.println(blue.resolvedSnapshotCacheSize());
System.out.println(blue.resolvedReferenceCacheSize());You can preload snapshots at startup:
blue.cacheResolvedSnapshot(first);Cache hits improve performance but do not change document identity or processor gas accounting.
A dictionary is a named collection of known Blue type definitions. When you send a document to another system, that system may tell you which dictionaries it understands. The exporter can then keep supported types as compact BlueId references and inline unsupported type definitions so the receiver still gets a self-describing document.
Register dictionaries through the generic TypeDictionary SPI:
import blue.language.dictionary.TypeDictionary;
blue.registerTypeDictionary(myDictionary);Export for a receiver that supports one dictionary version:
import blue.language.dictionary.ExportContext;
ExportContext context = ExportContext.builder()
.dictionary("example.types", "ExampleDictionaryBlueId")
.build();
String yaml = blue.nodeToYaml(document, context);
String json = blue.nodeToJson(document, context);If a referenced type belongs to example.types and is representable by
ExampleDictionaryBlueId, the exported document keeps the compact reference:
request:
type:
blueId: <SupportedRequestTypeBlueId>If a referenced type is known locally but not supported by the receiver, the exporter inlines the current type definition:
request:
type:
name: Custom Request
amount:
type:
blueId: <IntegerBlueId>
memo:
type:
blueId: <TextBlueId>Inlining is recursive and cycle-checked. The exporter transforms only type
metadata fields: type, itemType, keyType, and valueType. Ordinary data
references remain ordinary data references.
Disable fallback in strict integrations:
ExportContext strictContext = ExportContext.builder()
.dictionary("example.types", "ExampleDictionaryBlueId")
.inlineUnsupportedTypes(false)
.build();With fallback disabled, export fails if any known type cannot be represented by the requested dictionary context.
Matching answers: does this candidate node conform to this target type or pattern?
Node event = blue.yamlToNode(
"message:\n" +
" request:\n" +
" amount: 10\n" +
" currency: USD\n" +
" ignored:\n" +
" deeply: nested\n");
Node pattern = blue.yamlToNode(
"message:\n" +
" request:\n" +
" currency: USD\n");
boolean matches = blue.nodeMatchesType(event, pattern);For hot loops, match resolved immutable nodes:
ResolvedSnapshot eventSnapshot = blue.resolveToSnapshot(event);
ResolvedSnapshot patternSnapshot = blue.resolveToSnapshot(pattern);
boolean fast = blue.nodeMatchesType(
eventSnapshot,
"/message/request",
patternSnapshot.resolvedAt("/message/request"));The mutable compatibility matcher resolves only paths observed by the target pattern. The frozen matcher avoids mutable traversal entirely and reuses provider-backed references through local caches.
Important matching rules:
nameanddescriptionare labels, not type-compatibility constraints;- pure reference pattern leaves are exact identity checks;
- extra candidate fields are allowed unless the pattern/schema forbids them;
- list and dictionary payload kinds are checked explicitly;
- missing optional target fields are allowed unless they carry meaningful
requirements such as
schema.required: true.
Canonical patches operate on immutable roots and return new snapshots.
import blue.language.processor.model.JsonPatch;
import blue.language.snapshot.ResolvedSnapshot;
ResolvedSnapshot before = blue.resolveToSnapshot(price);
JsonPatch patch = JsonPatch.replace("/amount", new Node().value(200));
ResolvedSnapshot after = blue.applyCanonicalPatch(before, patch);
System.out.println(after.blueId());Supported patch operations:
JsonPatch.add(path, value)JsonPatch.replace(path, value)JsonPatch.remove(path)
Patch paths are JSON Pointers. Object keys containing / or ~ must be
escaped as ~1 and ~0.
Patch-time minimization removes redundant overrides where possible. If a patch writes a value equal to inherited resolved state, the canonical override can be removed rather than preserved.
Document processing must never commit an illegal snapshot. If a patch violates the current declared type, the processor can generalize the affected node upward through the type hierarchy.
Example:
type: Price in EUR
amount: 150
currency: EURIf a processor changes currency to USD, the node can no longer honestly
claim to be Price in EUR. It may generalize to the parent type Price, then
ancestors are checked up to the root.
The generalization flow is transactional:
- plan the immutable patch;
- check conformance from changed paths upward;
- add canonical type/generalization patches where needed;
- commit the new snapshot only if the whole plan succeeds;
- roll back on failure.
WorkingDocument is a frozen preview state for processor-side read-your-writes
logic. It uses the same immutable patch transaction as the processor runtime,
including conformance checks, dynamic type generalization, and Type
Generalization Policy enforcement, but it does not commit to the active
processor runtime.
import blue.language.processor.ProcessorExecutionContext;
import blue.language.processor.WorkingDocument;
import blue.language.processor.model.JsonPatch;
WorkingDocument working = context.newWorkingDocument();
working.applyPatch(JsonPatch.replace("/price/currency", new Node().value("USD")));
String currency = (String) working.resolvedAt("/price/currency").getValue();Working previews do not emit Document Update cascades, charge gas, update checkpoints, or write termination/marker state. Contract processors should preview first and buffer actual effects only after preview succeeds:
working.applyPatches(patches);
context.applyPatches(patches);Use materializeCanonicalRoot(), materializeResolvedRoot(), commitToNode(),
or commitSnapshot() only at explicit integration boundaries. Normal processor
reads should stay on FrozenNode roots and pointer lookups.
Java objects can be converted to and from Blue nodes.
import blue.language.Blue;
import blue.language.model.Node;
import blue.language.model.TypeBlueId;
@TypeBlueId("Person")
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Blue blue = new Blue();
Person alice = new Person();
alice.setName("Alice");
alice.setAge(34);
Node node = blue.objectToNode(alice);
Person copy = blue.nodeToObject(node, Person.class);@TypeBlueId declares the Blue type identity used by the mapper.
The library includes a generic document processor. It does not hard-code a business workflow language; instead, applications register processors for the contract types they understand.
Processor roles:
ChannelProcessor<T>decides whether an external event belongs to a channel;HandlerProcessor<T>decides whether a handler should run and executes it;ContractProcessor<T>is the base interface for marker-style contracts.
Minimal channel contract:
import blue.language.processor.model.ChannelContract;
public class ExampleChannel extends ChannelContract {
private String eventType;
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
}Minimal channel processor:
import blue.language.model.Node;
import blue.language.processor.ChannelEvaluationContext;
import blue.language.processor.ChannelProcessor;
public final class ExampleChannelProcessor implements ChannelProcessor<ExampleChannel> {
@Override
public Class<ExampleChannel> contractType() {
return ExampleChannel.class;
}
@Override
public boolean matches(ExampleChannel contract, ChannelEvaluationContext context) {
Object eventType = context.event().getProperties().get("eventType").getValue();
return contract.getEventType().equals(eventType);
}
@Override
public String eventId(ExampleChannel contract, ChannelEvaluationContext context) {
Node id = context.event().getProperties().get("eventId");
return id == null ? null : String.valueOf(id.getValue());
}
}Minimal handler contract:
import blue.language.processor.model.HandlerContract;
public class SetCounter extends HandlerContract {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}Minimal handler processor:
import blue.language.model.Node;
import blue.language.processor.HandlerProcessor;
import blue.language.processor.ProcessorExecutionContext;
import blue.language.processor.model.JsonPatch;
public final class SetCounterProcessor implements HandlerProcessor<SetCounter> {
@Override
public Class<SetCounter> contractType() {
return SetCounter.class;
}
@Override
public void execute(SetCounter contract, ProcessorExecutionContext context) {
context.applyPatch(JsonPatch.replace(
context.resolvePointer("/counter"),
new Node().value(contract.getValue())));
}
}Register processors and run a document:
import blue.language.Blue;
import blue.language.model.Node;
import blue.language.processor.DocumentProcessingResult;
Blue blue = new Blue();
Node exampleChannelType = new Node().name("ExampleChannel");
String exampleChannelBlueId = blue.calculateBlueId(exampleChannelType);
Node setCounterType = new Node().name("SetCounter");
String setCounterBlueId = blue.calculateBlueId(setCounterType);
blue.registerExternalContractType(exampleChannelBlueId, exampleChannelType, new ExampleChannelProcessor())
.registerExternalContractType(setCounterBlueId, setCounterType, new SetCounterProcessor());
Node document = blue.yamlToNode(
"name: Counter\n" +
"counter: 0\n" +
"contracts:\n" +
" events:\n" +
" type:\n" +
" blueId: " + exampleChannelBlueId + "\n" +
" eventType: counter.set\n" +
" setCounter:\n" +
" type:\n" +
" blueId: " + setCounterBlueId + "\n" +
" channel: events\n" +
" value: 10\n");
Node event = blue.yamlToNode(
"eventId: evt-1\n" +
"eventType: counter.set\n");
DocumentProcessingResult result = blue.processDocument(document, event);
System.out.println(result.blueId());
System.out.println(result.totalGas());
System.out.println(blue.nodeToYaml(result.document()));External contract processors must register the canonical type node for the
BlueId they handle. The runtime checks that every active contract in the
initial processing closure is understood; if not, processing fails before state
is mutated. processDocument(document, event) is the normative one-call
PROCESS API and initializes scopes as part of the run when needed.
String yaml = blue.nodeToYaml(node);
String simpleYaml = blue.nodeToSimpleYaml(node);
String json = blue.nodeToJson(node);
String simpleJson = blue.nodeToSimpleJson(node);The normal serializers preserve Blue metadata. The simple serializers are useful when you want a simpler projection for display or application-facing output.
Primary facade:
yamlToNode(String)jsonToNode(String)nodeToYaml(Node)nodeToJson(Node)objectToNode(Object)nodeToObject(Node, Class<T>)calculateBlueId(Node)calculateSemanticBlueId(Node)exportNode(Node, ExportContext)resolve(Node)canonicalize(Node)resolveToSnapshot(Node)loadSnapshot(String blueId)applyCanonicalPatch(ResolvedSnapshot, JsonPatch)nodeToJson(Node, ExportContext)nodeToYaml(Node, ExportContext)nodeMatchesType(Node, Node)nodeMatchesType(FrozenNode, FrozenNode)nodeMatchesType(ResolvedSnapshot, String, FrozenNode)initializeDocument(Node)processDocument(Node, Node)processDocument(ResolvedSnapshot, Node)conformanceReport()runConformanceSuite()contractsConformanceReport()runContractsConformanceSuite()registerContractProcessor(...)registerExternalContractType(...)registerTypeDictionary(...)
Mutable Blue document tree. Best for parsing, authoring, compatibility, and serialization boundaries.
Immutable Blue node with cached BlueId and path-index helpers. Best for runtime internals and repeated reads.
Immutable canonical/resolved pair. Best for document-processing state.
Reference lookup boundary for { blueId: ... } nodes.
Included providers:
BasicNodeProviderCachingNodeProviderClasspathBasedNodeProviderDirectoryBasedNodeProviderSequentialNodeProvider
Shared type/shape matcher. NodeTypeMatcher is the mutable compatibility
adapter. FrozenTypeMatcher is the fast path for resolved immutable graphs.
Implemented and covered by tests:
- strict canonical language core;
- RFC 8785-style canonical BlueId hashing for supported scalar/list/object cases;
- deterministic integer and typed-Double handling;
- reference-only
blueIdsemantics; - payload-kind exclusivity;
- schema validation for deterministic core keywords;
- list control forms and reverse minimization;
- circular self-reference ingestion;
- immutable snapshots with path indexes and resolved type cache reuse;
- canonical overlay patching and patch-time minimization;
- dynamic type generalization with rollback;
- fast frozen type/pattern matching;
- snapshot-backed document processing runtime;
- Blue Contracts and Processor 1.0 runtime registry and conformance fixtures;
- external channel/handler/marker processor SPI with explicit canonical type registration.
Known boundaries:
- cross-language golden fixtures are still needed for independent implementation certification;
- provider ingestion stores strict canonical/preprocessed content and does not default to semantic resolve/minimize storage;
- conformance/generalization is snapshot-safe at the boundary but still bridges through mutable resolver internals in some checks;
- concrete business contracts are supplied by applications through explicitly registered processors and canonical type nodes;
- canonical-plus-bundle transport/webhook export is not part of this module yet.
For deeper design notes, see:
- Canonical Language Core
- Frozen Type Matching
- Processor Contract Matching
- Snapshots, Patching, And Generalization
The project currently compiles for Java 8 source/target compatibility and uses JUnit 5 for tests. Build and test with a JDK that can run Gradle 8.4.
Run the full CI-style verification command:
./gradlew clean testRun the test suite without cleaning:
./gradlew testRun only the Blue Language 1.0 conformance fixtures:
./gradlew test --tests '*BlueLanguageConformanceFixtureTest'Run only the Blue Contracts and Processor 1.0 conformance fixtures:
./gradlew test --tests '*BlueContractsConformanceFixtureTest'At runtime, new Blue().conformanceReport() returns static Blue Language 1.0
metadata: language version, core registry BlueIds, fixture package identity,
fixture IDs, and fixture categories. new Blue().runConformanceSuite() executes
the manifest-driven fixture suite and returns passed fixture IDs plus detailed
failures with fixture ID, category, operation, exception class, and message.
The fixture package under src/test/resources/blue-language-1.0/fixtures is a
vendored copy of the canonical Blue Language 1.0 fixture package; its manifest
identity must match the fixture package identity published by the Blue Language
1.0 specification release. The current Java fixture package identity is a
SHA-256 content digest over manifest.yaml with the identity field blanked plus
each manifest-listed fixture file in manifest order; verify it with
BlueConformanceReport.fixturePackageIdentityMatchesFixtureFiles().
At runtime, new Blue().contractsConformanceReport() returns static Blue
Contracts and Processor 1.0 metadata: fixture package identity, required fixture
IDs, fixture IDs, categories, and coverage checks.
new Blue().runContractsConformanceSuite() executes the separate contracts
fixture suite. The contracts fixture package under
src/test/resources/blue-contracts-1.0/fixtures is vendored from the official
Blue Contracts 1.0 spec repository. Its release identity is
sha256:2f197ca3bbdc41b75e772777cc48e51019754347e1bee26b5f3209b71d9bd9ca.
The runtime registry resources are vendored from
contract/1.0/registry/blue-contracts-1.0. The fixture package uses the same
SHA-256 content digest scheme; verify it with
BlueContractsConformanceReport.fixturePackageIdentityMatchesFixtureFiles()
and contractsConformanceReport().isOfficialContracts10FixturePackage().
For release checks, both language and contracts reports should have no failures,
all fixture IDs passed, required fixture coverage, exact required fixture sets,
and matching fixture package identities.
Build jars:
./gradlew buildPublish to local Maven:
./gradlew publishToMavenLocalThe Gradle wrapper uses the distribution declared in
gradle/wrapper/gradle-wrapper.properties. Local and CI environments need either
network access for that first wrapper download or a cached Gradle distribution;
offline verification works once the wrapper distribution and normal dependency
cache are already present.
src/main/java/blue/language
Blue.java primary facade
model/ Node, Schema, serializers, annotations
merge/ type resolution and merge pipeline
preprocess/ alias/default-blue preprocessing
provider/ BlueId content providers
snapshot/ FrozenNode and ResolvedSnapshot
processor/ generic document processor runtime
conformance/ type conformance and generalization
utils/ BlueId, matching, JSON pointer, helpers
docs/
canonical-language-core.md
frozen-type-matching.md
processor-contract-matching.md
snapshots-patching-and-generalization.md
specification-implementation-gaps.md
- Blue language specification: https://language.blue/docs/reference/specification
- Source repository: https://github.com/bluecontract/blue-language-java