Generating Java Classes
This guide explains how to generate Java binding classes from Metaschema modules.
When working with structured data, you have several options: parse XML/JSON manually, use generic tree structures like DOM or JsonNode, or use strongly-typed classes. Strongly-typed classes offer significant advantages:
- Compile-time safety - The compiler catches type errors before runtime
- IDE support - Code completion, refactoring, and navigation work automatically
- Self-documenting code - Method names like
getCatalog().getMetadata().getTitle()clearly express intent - No boilerplate - Serialization and deserialization are handled automatically
The challenge is keeping your Java classes synchronized with your data model. If the model changes, you need to update the classes—a tedious and error-prone process. The Metaschema Maven plugin solves this by generating Java classes directly from Metaschema module definitions. When your model changes, regenerate the classes and the compiler tells you what code needs updating.
The Metaschema Maven plugin integrates into your Maven build to generate Java classes from Metaschema module definitions. These generated classes provide:
- Type-safe data binding - Fields have proper Java types matching Metaschema definitions
- Automatic serialization - Read and write XML, JSON, and YAML without manual parsing
- IDE integration - Full code completion and type checking in your IDE
- Compile-time validation - Catch errors at build time rather than runtime
Add to your pom.xml:
<build>
<plugins>
<plugin>
<groupId>dev.metaschema.java</groupId>
<artifactId>metaschema-maven-plugin</artifactId>
<version>3.0.0.M2</version>
<executions>
<execution>
<id>generate-sources</id>
<goals>
<goal>generate-sources</goal>
</goals>
<configuration>
<metaschemaDir>src/main/metaschema</metaschemaDir>
<includes>
<include>my-model_metaschema.xml</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<configuration>
<!-- Source directory containing Metaschema files -->
<metaschemaDir>src/main/metaschema</metaschemaDir>
<!-- Which files to include -->
<includes>
<include>**/*_metaschema.xml</include>
</includes>
<!-- Files to exclude -->
<excludes>
<exclude>**/draft-*.xml</exclude>
</excludes>
<!-- Output directory for generated sources -->
<outputDirectory>/home/runner/work/metaschema-java/metaschema-java/target/generated-sources/metaschema</outputDirectory>
</configuration>
# Generate sources
mvn generate-sources
# Or as part of full build
mvn compile
Generated classes appear in target/generated-sources/metaschema/.
<define-assembly name="catalog">
<formal-name>Catalog</formal-name>
<description>A collection of controls.</description>
<root-name>catalog</root-name>
<define-flag name="uuid" required="yes">
<formal-name>Catalog UUID</formal-name>
</define-flag>
<model>
<assembly ref="metadata" min-occurs="1"/>
<assembly ref="group" max-occurs="unbounded"/>
</model>
</define-assembly>
@MetaschemaAssembly(
name = "catalog",
rootName = "catalog"
)
public class Catalog {
@BoundFlag(name = "uuid", required = true)
private UUID uuid;
@BoundAssembly(minOccurs = 1)
private Metadata metadata;
@BoundAssembly(maxOccurs = -1)
private List<Group> groups;
// Getters and setters
public UUID getUuid() { return uuid; }
public void setUuid(UUID uuid) { this.uuid = uuid; }
public Metadata getMetadata() { return metadata; }
public void setMetadata(Metadata metadata) { this.metadata = metadata; }
public List<Group> getGroups() { return groups; }
public void setGroups(List<Group> groups) { this.groups = groups; }
}
| Metaschema Type | Java Type |
|---|---|
uuid |
java.util.UUID |
date-time |
java.time.ZonedDateTime |
string |
String |
integer |
java.math.BigInteger |
boolean |
Boolean |
uri |
java.net.URI |
markup-line |
MarkupLine |
markup-multiline |
MarkupMultiline |
| Metaschema | Java |
|---|---|
min-occurs="0" |
Nullable field |
min-occurs="1" |
@NonNull on getter/setter methods |
max-occurs="1" |
Single instance |
max-occurs="unbounded" |
List<T> |
Generated classes include annotations for:
@MetaschemaAssembly // Assembly definitions
@MetaschemaField // Field definitions
@BoundFlag // Flag instances
@BoundField // Field instances
@BoundAssembly // Assembly instances
Catalog catalog = new Catalog();
catalog.setUuid(UUID.randomUUID());
Metadata metadata = new Metadata();
metadata.setTitle(MarkupLine.fromMarkdown("My Catalog"));
metadata.setLastModified(ZonedDateTime.now());
catalog.setMetadata(metadata);
import dev.metaschema.databind.IBindingContext;
import dev.metaschema.databind.io.Format;
import dev.metaschema.databind.io.ISerializer;
IBindingContext context = IBindingContext.instance();
ISerializer<Catalog> serializer = context.newSerializer(Format.JSON, Catalog.class);
serializer.serialize(catalog, Path.of("catalog.json"));
IDeserializer<Catalog> deserializer = context.newDeserializer(
Format.JSON, Catalog.class);
Catalog catalog = deserializer.deserialize(Path.of("catalog.json"));
<configuration>
<includes>
<include>core_metaschema.xml</include>
<include>extension_metaschema.xml</include>
</includes>
</configuration>
If modules import each other:
<!-- core_metaschema.xml -->
<metaschema>
<define-assembly name="base">...</define-assembly>
</metaschema>
<!-- extension_metaschema.xml -->
<metaschema>
<import href="core_metaschema.xml"/>
<define-assembly name="extended">
<model>
<assembly ref="base"/>
</model>
</define-assembly>
</metaschema>
All imported modules are processed automatically.
Control package via namespace:
<metaschema xmlns="http://csrc.nist.gov/ns/oscal/metaschema/1.0"
xmlns:java="http://metaschema.dev/ns/java">
<schema-name>My Model</schema-name>
<namespace>http://example.com/ns/mymodel</namespace>
<!-- Package derived from namespace -->
</metaschema>
The plugin generates abstract base classes for extension:
// Generated abstract class
public abstract class AbstractCatalog {
// Generated fields and methods
}
// Your extension (in src/main/java)
public class Catalog extends AbstractCatalog {
// Additional methods
}
Symptom: No classes in target/generated-sources/
Fix:
- Check
metaschemaDirpath is correct - Verify
includespattern matches files - Run
mvn generate-sources -Xfor debug output
Symptom: Generated classes won't compile
Fix:
- Ensure all imported modules are accessible
- Check for circular dependencies
- Verify Metaschema syntax is valid
Symptom: Annotations not found
Fix: Add runtime dependency:
<dependency>
<groupId>dev.metaschema.java</groupId>
<artifactId>metaschema-databind</artifactId>
<version>3.0.0.M2</version>
</dependency>
- Use consistent naming - Follow
*_metaschema.xmlconvention - Organize by module - One module per domain concept
- Document in Metaschema - Descriptions become Javadoc
- Version your schemas - Track schema changes in VCS
- Don't edit generated code - Use extension classes instead
Continue learning about the Metaschema Java Tools with these related guides:
- Loading Modules - Load modules at runtime
- Reading & Writing Data - Use generated classes
- Architecture - Understand the generation pipeline

