The Java port targets Spring-Boot consumers on Maven. It ships the full metamodel
- loader + conformance + OMDB runtime persistence engine + the FR-004 render engine,
plus the
metaobjects-maven-pluginfor build-time codegen (meta:gen/meta:editor).
Schema migrations are owned by the TypeScript toolchain (@metaobjectsdev/cli migrate);
the Java diff-and-converge migration engine and its meta:migrate / live-DB-drift
meta:verify Maven goals were removed. Per ADR-0015 the OMDB runtime auto-create
path was also removed — OMDB is pure data-access (CRUD/query/codec/transactions).
Prompt / template drift is still checked via the metaobjects-render Verify API.
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>com.metaobjects</groupId>
<artifactId>metaobjects-metadata</artifactId>
<version>${metaobjects.version}</version>
</dependency>
<dependency>
<groupId>com.metaobjects</groupId>
<artifactId>metaobjects-omdb</artifactId>
<version>${metaobjects.version}</version>
</dependency>
<dependency>
<groupId>com.metaobjects</groupId>
<artifactId>metaobjects-render</artifactId>
<version>${metaobjects.version}</version>
</dependency>
</dependencies>For Spring integration: add metaobjects-core-spring.
<build>
<plugins>
<plugin>
<groupId>com.metaobjects</groupId>
<artifactId>metaobjects-maven-plugin</artifactId>
<version>${metaobjects.version}</version>
<executions>
<execution>
<id>generate</id>
<phase>generate-sources</phase>
<goals><goal>generate</goal></goals>
<configuration>
<loader>
<sourceDir>src/main/metaobjects</sourceDir>
</loader>
<generators>
<generator>
<classname>com.metaobjects.generator.java.JavaPojoGenerator</classname>
<args>
<outputDir>${project.build.directory}/generated-sources/java</outputDir>
</args>
</generator>
</generators>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>Drop metadata under src/main/metaobjects/:
Java uses SPI auto-discovery for type providers — drop your provider
class on the classpath, list its FQCN in
META-INF/services/com.metaobjects.registry.MetaDataTypeProvider, and
MetaDataRegistry.getInstance() will compose it in dependency order
alongside the core providers:
// src/main/java/com/example/providers/ExampleToolcallProvider.java
package com.example.providers;
import com.metaobjects.registry.MetaDataTypeProvider;
import com.metaobjects.registry.MetaDataRegistry;
public class ExampleToolcallProvider implements MetaDataTypeProvider {
@Override public String getProviderId() { return "example-template-toolcall"; }
@Override public String[] getDependencies() { return new String[] { "core-types" }; }
@Override public void registerTypes(MetaDataRegistry registry) {
// registry.register(...) — see the cross-port contract
}
}# src/main/resources/META-INF/services/com.metaobjects.registry.MetaDataTypeProvider
com.example.providers.ExampleToolcallProvider
The provider contract is structurally identical to TS / C# / Python (id +
dependencies + description + registerTypes body); the loader composes
all providers via Kahn's algorithm and emits the same stable error codes
on failure (ERR_PROVIDER_DUPLICATE_ID, _MISSING_DEPENDENCY,
_DEPENDENCY_CYCLE).
A programmatic MetaDataRegistry.compose(List<MetaDataTypeProvider>)
factory (matching the explicit-list approach used by TS / C# / Python) is
on the follow-up backlog for callers who want to bypass SPI auto-discovery
in tests or embedded scenarios. The conceptual reference lives in
../features/extending-with-providers.md.
mvn compile # runs the generate goal (bound to generate-sources)Schema migrations are not a Java-port concern — author them with the TypeScript
toolchain (@metaobjectsdev/cli migrate), then apply the resulting DDL to the
database OMDB connects to. OMDB itself is pure data-access; the former runtime
auto-create path was removed per ADR-0015.
OMDB reads the same metadata at runtime and drives CRUD; no per-entity ORM boilerplate.
import com.metaobjects.loader.MetaDataLoader;
import com.metaobjects.omdb.ObjectManagerDb;
import com.metaobjects.object.ValueObject;
import acme.blog.Author;
import java.nio.file.Path;
import java.util.List;
public class App {
public static void main(String[] args) throws Exception {
MetaDataLoader loader = MetaDataLoader.fromDirectory(
"app", Path.of("src/main/metaobjects"));
ObjectManagerDb om = ObjectManagerDb.builder()
.loader(loader)
.dataSource(/* javax.sql.DataSource */)
.build();
// CRUD
Author author = new Author();
author.setName("Ada");
om.persist(author);
List<Author> all = om.getObjectsBy(Author.class, new ValueObject());
Author fetched = om.getObjectById(Author.class, author.getId());
}
}Spring wiring lives in metaobjects-core-spring; declare an ObjectManagerDb
bean with the Spring DataSource and let Spring inject it into your services.
import com.metaobjects.render.*;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
Provider provider = new FilesystemProvider(Path.of("./prompts"));
String out = Renderer.render(RenderRequest.builder()
.ref("lobby/welcome")
.payload(Map.of(
"displayName", "Ada",
"postCount", 12L,
"posts", List.of(Map.of("title", "Hello"))))
.provider(provider)
.format("xml")
.build());Verify.verify(loader, provider, options) drift-checks every template.* node
against its @payloadRef. Wire it into a Maven test (e.g. a JUnit assertion in
the test phase).
| Generator | Module | Output |
|---|---|---|
SpringControllerGenerator |
metaobjects-codegen-spring |
One <Entity>Controller.java per writable entity (source.rdb @kind="table"). Spring Boot 3.x / Spring Web MVC. Five CRUD endpoints (GET list / GET by id / POST / PATCH + PUT / DELETE) matching the cross-port REST API contract. ?sort, ?limit/?offset, ?withCount=1 envelope, 404 + 400 envelopes per the contract. Filter operators (eq/ne/gt/gte/lt/lte/in/like/isNull) ship via the generated <Entity>FilterAllowlist (SpringFilterAllowlistGenerator) + the runtime FilterParser, wired directly into the list handler. |
SpringDtoGenerator |
metaobjects-codegen-spring |
One <Entity>Dto.java per entity as a Java 21 record. Wrapped-primitive components (Long, Integer, Boolean) so missing JSON properties deserialise to null. Currency = Long (integer minor units cross-port invariant). Used as both request and response body. |
SpringRepositoryGenerator |
metaobjects-codegen-spring |
One <Entity>Repository.java per writable entity as a hand-stubbed Java interface the consumer implements with their preferred persistence layer (Spring Data JPA / jOOQ / plain JDBC — all out of MetaObjects' concern). Nests the SortClause record the controller calls into. |
Wire any of them via the Maven plugin's <generator> entry pointing at
com.metaobjects.generator.spring.SpringControllerGenerator /
SpringDtoGenerator / SpringRepositoryGenerator. The three are
independently configurable; typical use is all three together (controller +
DTO + repository).
The browser-side Angular 18 client (@metaobjectsdev/angular +
@metaobjectsdev/codegen-ts-angular, both shipped on the TypeScript side per
the universal client recipe) interoperates with the
generated Spring controllers out of the box — the cross-port URL grammar and
JSON wire shape are identical. Consumers wire EntityFetcherToken to a
fetch wrapper that targets their Spring backend's apiPrefix (default
/api); no Java-specific Angular code is needed.
CORS is the only typical hookup item: a Spring dev-server on port 8080 + an
Angular dev-server on port 4200 will need @CrossOrigin on the generated
controllers (or a global WebMvcConfigurer addCorsMappings(...) registration
in the consumer's @Configuration). The generated controllers do not emit
@CrossOrigin — adding it cross-port would require a CORS-policy
configuration model that has not yet been specced.
| Feature | Status |
|---|---|
| Entities + fields | Yes |
| Relationships + FK | Yes (via OMDB) |
| Source kinds (table / view / storedProc) | Yes |
field.currency / field.enum / field.object + @storage |
Yes |
| Templates + render (FR-004) | Yes (metaobjects-render) |
| Payload-VO codegen | Yes — SpringPayloadGenerator (in metaobjects-codegen-spring) emits a Java 21 record per template, mirrors the Kotlin shape |
| Output parser codegen (FR-006) | Yes — SpringOutputParserGenerator (in metaobjects-codegen-spring) — see usage below |
| Migrations | TS-only (@metaobjectsdev/cli migrate) — the Java migration engine and the OMDB runtime auto-create path were both removed (ADR-0015); apply the TS-produced DDL to the database |
| Drift verify | Renderer.verify / Verify.verify (prompts). Live-DB schema-drift verification is part of the TS migration toolchain |
| Runtime metadata | Full — OMDB ObjectManager |
| REST controller codegen | Spring Web MVC — metaobjects-codegen-spring (FR-008 §2.1) |
SpringOutputParserGenerator (in metaobjects-codegen-spring) emits one
<TemplateName>Parser Java class per template.output declaration — a
Jackson-backed, throw-only parser around the @payloadRef payload record
SpringPayloadGenerator already emits (no payload-shape re-declaration).
Registered in the module's generator registry as output-parser.
// generated/NpcResponseParser.java
public final class NpcResponseParser {
private static final ObjectMapper MAPPER = new ObjectMapper();
private NpcResponseParser() {}
/** @throws JsonProcessingException on malformed JSON or a schema mismatch. */
public static NpcResponsePayload parse(String text) throws JsonProcessingException {
return MAPPER.readValue(text, NpcResponsePayload.class);
}
}Consumer wiring:
String llmResponse = myLlmClient.complete(promptText);
try {
NpcResponsePayload npc = NpcResponseParser.parse(llmResponse);
return ResponseEntity.ok(npc);
} catch (JsonProcessingException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}Verify.verify(loader, provider, options) walks template.output nodes the
same way it walks template.prompt, catching payload-VO ↔ parser drift at
build time. Cross-port design is at
ADR-0010;
the feature reference is at
features/templates-and-payloads.md.
FR-010's tolerant extractLenient(loader, text) variant (returns an
ExtractionResult<TPayload> instead of throwing) ships alongside parse().
Per-corpus pass counts move every release — see
docs/CONFORMANCE.md for the current, authoritative
per-port numbers (metamodel, YAML, render, verify, persistence, API
contract). Java is green across all six active corpora today (Java doesn't
run the persistence corpus's migration scenarios — those are TS-only,
ADR-0015).
server/java/README.md— module-level overviewdocs/features/— every feature shows the Java output inline- Kotlin port — built on top of this Java tier with idiomatic Kotlin codegen
docs/superpowers/specs/2026-05-25-fr-004-java-template-port-design.md