An Ecore model of the Maven POM, generated from the Maven 4.0.0 XML schema and then lifted onto the Nasdanika model tower. Generation from the schema means the model is complete and stays faithful to Maven - every element, every attribute, and Maven’s own documentation carried through as Ecore documentation. Lifting it onto the tower means a POM is no longer a build file that happens to be parsed into objects: it is an architecture element with owners, work, controls, risks, lifecycle stage, and threats attached.

The model sits on the threat floor: Model - the POM itself - extends the threat model’s Asset, which extends the architecture model’s Element, which extends work Workable, which extends governance Governed. Through that spine it also inherits decision analysis and decision binding, lifecycle, seal, IAM, role, and nxcore. None of that is declared in this model. It arrives by inheritance, which is the point of the tower.

Competitive landscape

Everyone reads POMs. Almost nobody keeps them.

Maven’s own tooling. dependency:tree, dependency:analyze, versions-maven-plugin, the reactor, the site reports. Authoritative, exact, and scoped to one build on one machine at one moment. The output is text for a human. There is no estate, no history, and no second project in view - dependency:tree cannot tell you that forty other services depend on the artifact you are about to change.

Repository managers. Nexus, Artifactory, and their scanners hold every POM ever published and index them well. But they see published artifacts, not the source estate: no branches, no unreleased modules, no POM that never got deployed, and no connection to who wrote it. The repository knows com.acme:billing-core:3.2.1 exists; it does not know it belongs to the Payments tribe, is two majors behind on a library under a sunset control, and has three tech-debt items open against it.

SCA and SBOM. Snyk, Dependabot, Renovate, OWASP Dependency-Check, CycloneDX/SPDX feeding Dependency-Track. The strongest camp, and genuinely good at “which advisory affects which artifact.” Their shape is the limitation: an SBOM is a flat per-build list, so the estate-wide graph exists only inside a particular tool’s private store, only for what was uploaded to it, and only in dependency terms. Ownership, governance status, coverage, and work are other systems’ problems.

Developer portals and code intelligence. Backstage’s software catalog, Sourcegraph, homegrown Neo4j loaders. Backstage models ownership properly but its catalog is hand-written YAML that drifts from the POM the day after it is committed, and its dependency edges are declared by humans rather than derived from the build. Homegrown graph loaders get the derivation right and then stall, because every new question means a new ad-hoc schema.

The actual competitor is a spreadsheet of services, a wiki page of who owns what, and a script that walked the repository manager once, in a quarter nobody remembers.

What a typed model adds

A POM is an element, not a file. Because Model is an architecture Element, a POM participates in containment (multi-module reactors are element hierarchies), in typed Relationships, and in views - alongside C4 containers, services, and capabilities from the rest of the estate. Nothing needs exporting into a diagramming tool, because the diagram is a rendering of the same objects.

Dependencies become edges with semantics. Dependency, Parent, Plugin, and Extension are already objects with GAV coordinates; on the tower they resolve to Relationships between elements, or to ElementReferences when the other end lives in another model - a different repository, a different org unit, or Maven Central. Federation is the normal case, not an integration project: an org-wide graph is thousands of small models cross-referencing each other by URI.

People resolve to people. Developer and Contributor carry id, email, organization, and roles. Those map onto role Actors and RoleAssignments and, from there, onto Person and OrganizationalUnit in the org design model. An email in a POM stops being a string and becomes an edge into the org hierarchy - which has been done in practice, and is what makes roll-up reporting along the org tree possible.

Aspects arrive without being modeled. Tech debt on a module is work, attached because Element is Workable. “This module must publish coverage” is a governance Control with a ControlApplication and, when it cannot be met, a Waiver. Deprecation and sunset are lifecycle Stages. “Why did we standardize on this HTTP client” is a decision analysis Alternative with recorded judgments, and the POM entry is its Binding. A vulnerable dependency is a Threat promoted into the governance risk register. All of that composes onto POMs because of where the model sits, not because the Maven schema was extended.

Branches are a dimension. POMs are read from Git, so the same module exists at many refs. Comparing main against a release branch is a model diff, and “how far has this fleet drifted” is a query rather than a survey.

Loading

Two loading paths, deliberately kept separate.

Literal. A resource factory parses pom.xml into this model exactly as written - placeholders unresolved, inheritance unapplied, profiles inert. This is the right view for governance and provenance questions: what does this file actually say, and who changed it.

Effective. A loader over the Maven Model Java API builds the model from Maven’s own resolved objects: interpolated properties, inherited parent configuration, activated profiles. This is the right view for dependency and reuse analysis, since it is what the build actually sees. (The loader is not in the codebase yet; it will be added on first use.)

Remote sources. POMs rarely sit on the local disk. The GitLab model contributes a gitlab:// URIHandler, so a POM anywhere in the estate is addressable as gitlab://<server>/<project>/pom.xml and loads through the same resource set - including at a specific branch, which is where the branch dimension above comes from. The Git model covers cloned repositories. This combination has been used to sweep tens of thousands of repositories and assemble an org-wide dependency model. There is also Maven URI Handler for loading resources, including pom.xmls from Maven repositories.

Prototypes and enrichment. pom.xml says only what Maven’s schema allows, which is a small subset of what is worth knowing about a module. A sibling pom.xml.groovy - a Groovy DSL, or any other format a loader is written for - supplies a prototype that the loaded POM is merged onto: governance controls, known vulnerabilities and their waivers, tech-debt work items, lifecycle stage, decision records. Further enrichment comes from files that already exist: CODEOWNERS for ownership, module-info.java via the Java model for real module boundaries, Git history for actual committers as opposed to declared ones, and source scanning for module size - so “who owns this” can be answered from evidence and cross-checked against the declaration.

Applications

Education

Most developers know the twenty POM elements they use and none of the rest. The generated documentation - every type, every feature, Maven’s own prose, with diagrams and cross-references - is a reference for the whole schema, browsable rather than searched for on Stack Overflow.

The interesting version is organizational. Organizations constrain Maven heavily: mandated parent POMs, banned plugins, approved repositories, required properties. A central function can take the generic documentation and publish an org-specific edition where those constraints are stated next to the element they constrain; org units can specialize further.1 The documentation of the schema and the documentation of the policy become one artifact.

Documentation generation

For a single project: a generated site of modules, dependencies, plugins, profiles, and contributors, cross-referenced and diagrammed. For an estate: the same generation over a federated model, with roll-ups along the org hierarchy and trends against a stored baseline.

A federated dependency graph

The flagship application. Sweep the estate, load every POM at every branch of interest, resolve the coordinates, and the result is one graph in which:

  • Reuse is measurable. Not “is this library popular” but which modules depend on it, on which versions, owned by which teams, on which branches. The inverse - a module nothing depends on - is equally useful and much harder to get any other way.
  • Vulnerability propagation is transitive and org-aware. An advisory against a transitive dependency yields the affected modules, their owners, and their org units in one query. Enriched with runtime call graphs from telemetry backends - cross-service edges no POM records - this extends past the build graph into what actually calls what, which is the difference between “1,400 services are affected” and “nine of them are on the path.”
  • Change impact is a query. Before a breaking change: everything downstream, transitively, with owners to notify.
  • Ownership is verified rather than asserted. POM developers, CODEOWNERS, and Git committers are three independent signals over the same modules. Where they disagree, someone should look.

Coverage as a published artifact

The coverage model and its JaCoCo .exec loader make coverage a first-class model. Published to the Maven repository alongside sources and Javadoc, coverage becomes authoritative - declared by the producing build, consumed by anyone - instead of a discovery exercise re-run by whoever is asking this quarter.

That unlocks transitive coverage: bytecode analysis of which methods a module actually calls into its dependencies, combined with the dependencies’ published coverage, gives real coverage of the code a service exercises. (Reflection escapes this, as it escapes every static approach.) It also closes a well-worn loophole: where coverage is mandated for services but not libraries, teams move the functionality into libraries and the number goes green while nothing is tested. Transitive coverage measures the code, not the packaging.

Work and delivery

Where the tracker of record is Jira, the Jira model pulls issues and work items and attaches them as work on the modules they concern - remediation, upgrades, tech debt - because Element is Workable. “What is in flight against this module” and “what modules does this epic touch” become two directions of the same query.

Export

Neo4j for graph analysis, so analysts can work in Cypher over the loaded estate. CycloneDX/SPDX for the SBOM ecosystem. The posture is the same as elsewhere in the tower: interoperate with the tools without living in them.

Cross-technology dependency tracking

Nothing above is Maven-specific except the parsing. npm, NuGet, PyPI, Go modules, and container images have equivalent models and loaders; all of them lift onto the same architecture Element and Relationship. A Java service depending on a Python service depending on an npm package is one graph with one query language, which no per-ecosystem tool provides and which is the shape of the answer most estates actually need.

Model overview

Generated from the Maven 4.0.0 XSD, so the class list mirrors the schema.

Area Types
Project Model (the POM root), Parent, Prerequisites, DocumentRoot
Dependencies Dependency, DependencyManagement, Exclusion
Build Build, BuildBase, Plugin, PluginExecution, PluginManagement, Extension, Resource
Profiles Profile, Activation, ActivationOS, ActivationProperty, ActivationFile
Repositories Repository, RepositoryPolicy, DeploymentRepository, DistributionManagement, Relocation, Site
People Developer, Contributor, Organization, MailingList, Notifier
Project metadata License, Scm, IssueManagement, CiManagement
Reporting Reporting, ReportPlugin, ReportSet
Inherited architecture Element/Relationship, work Work, governance Control/Risk/Waiver, lifecycle Stage, role Actor/RoleAssignment, threat Threat

Schema wrapper types (DependenciesType, PluginsType1, ConfigurationType2, …) exist because the XSD wraps repeated elements; they are structural and rarely of interest when querying.

Resources