graphitron-maven-plugin exposes three Maven goals: generate (the build-time codegen pipeline), validate (a faster schema-only run that emits no Java), and dev (the editor-loop server). Each goal accepts the same handful of <configuration> parameters; dev adds four of its own. One of the parameters (<schemaInputs>) is a list-of-complex-objects with its own POM binding.

This page is the parameter-by-parameter reference. The tutorial’s first chapter introduces the canonical <configuration> shape with one schema input and the two required packages; the directive reference under reference/directives covers what each schema input declares.

Goals

Goal Default phase Purpose Required parameters

generate

generate-sources

Run the rewrite pipeline (load → attribute → classify → validate → emit) and write Java sources under <outputDirectory>. Adds <outputDirectory> as a compile source root so the consumer’s compile phase picks the generated code up automatically.

<outputPackage>, <jooqPackage>

validate

validate

Run load → attribute → classify → validate only. No Java is emitted; the pipeline stops after validation. Useful as a CI gate when you want a schema check without paying for the full code generation.

(none; both packages are optional, a sentinel is substituted)

dev

(none; CLI-only)

Run the LSP server on 127.0.0.1:8487 and watch <schemaInputs> for writes matching <schemaFileExtensions> (default .graphqls / .graphql) plus the consumer’s compiled jOOQ output for .class changes. Re-runs the generator on every save (debounced) and rebuilds the in-process catalog atomically. Stop with Ctrl+C.

<outputPackage>, <jooqPackage>

The plugin coordinates are no.sikt:graphitron-maven-plugin; bind a goal to a <execution> block under the rewrite plugin’s <executions> to wire it into the standard Maven lifecycle, or invoke from the command line with mvn graphitron:<goal>.

Shared parameters

The following parameters apply to every goal:

Name Type Default Description

outputDirectory

String (path)

${project.build.directory}/generated-sources/graphitron

Directory the generate goal writes Java sources into. Resolved against the project basedir if relative. The generate goal also adds this directory as a compile source root so the consumer’s compile phase picks up the generated code; validate and dev honour the parameter for their own context-construction but do not add the source root.

outputPackage

String (Java package)

(none)

Root Java package the generator writes under. Required for generate and dev; optional for validate (a sentinel package is substituted because the validate goal never emits code). The Graphitron facade lands at <outputPackage>.Graphitron and GraphitronContext at <outputPackage>.schema.GraphitronContext.

jooqPackage

String (Java package)

(none)

Root Java package of the consumer’s jOOQ-generated catalog (the Tables / Keys / Routines classes). Required for generate and dev; optional for validate. The classifier resolves every @table and @field against the catalog rooted at this package.

graphName

String

${project.artifactId}

Name this module’s graph carries in the fact store. The module’s own artifactId is unique within a reactor and is the right answer unless the subgraph’s name differs from the module’s. Set it when two modules would otherwise claim one name, or when the graph has a published subgraph name of its own.

supergraph

String

(none)

Which supergraph this module’s graph is a subgraph of. A declared fact, because neither available inference answers it: two subgraphs of two different supergraphs carry indistinguishable federation SDL, and one checkout can hold two supergraphs plus standalone graphs while one supergraph can span checkouts. Paired with graphName it is the addressing federation already uses, so a parent pom shared by one supergraph’s modules can declare the value once in pluginManagement and every subgraph module inherits it.

Grouping, not federation: declaring it neither makes the graph federated nor is checked against the SDL’s @link opt-in, so a subgraph under development may declare its home before its first @key is written. Omit for a standalone graph; standalone is the default rather than a state you spell.

storeDirectory

File (path)

a per-user cache directory

Where the fact store is kept between runs, so a build starts from the previous run’s rows instead of re-reading every jar on the classpath. The default is your platform’s cache location for per-user tool state ($XDG_CACHE_HOME/graphitron/model/ or ~/.cache/graphitron/model/ on Linux, ~/Library/Caches/graphitron/model/ on macOS, %LOCALAPPDATA%\graphitron\model\ on Windows), with one store per project checkout, shared by that checkout’s modules. Set this parameter, or pass -Dgraphitron.store.directory=…​, to keep the store inside the build instead: a hermetic CI job that wants nothing written outside the workspace, or a container that discards $HOME. The store holds no state of record and is rebuilt from your sources whenever it cannot be read, so deleting it is always safe and never loses anything; it is a cache, not build output, which is why mvn clean no longer removes it.

schemaInputs

List<SchemaInputBinding>

empty

List of <schemaInput> entries pointing at schema files (or globs that expand to schema files) the loader assembles into one schema. By default schema files are those ending in .graphqls or .graphql; the <schemaFileExtensions> parameter narrows or extends that set. Order matters when two files declare the same type with overlapping fields; later entries win. See <schemaInput> binding for the per-entry shape.

schemaFileExtensions

List<String>

[".graphqls", ".graphql"]

File-name suffixes that count as GraphQL schema files. Drives three places at once: the <schemaInputs> post-scan filter (matches are kept only when the filename ends in a configured suffix), the graphitron:dev watcher’s trigger filter (only writes matching a configured suffix fire a regenerate), and the orphan-file scanner that runs when schema validation fails (only files with a configured suffix can be reported as orphans). Leading dots are optional (graphqls normalises to .graphqls); case is preserved. A configured but empty list is rejected at Mojo execute; omit the parameter to accept the default.

lint

LintBinding

(none)

Lint suppression. A <lint> block naming rule ids to silence everywhere (<disabledRules>) and type-name globs to skip in the SDL lint engine (<excludedTypes>). Applied build-side, so the build log, the graphitron:dev editor squiggles, and the MCP diagnostics tool suppress identically. A disabled rule id that resolves to no rule fails the build with the list of valid ids. Omit the block to lint every author-owned type with every rule. See Silencing lint warnings.

sessionState

SessionStateBinding

(none)

Session identity. A <sessionState> block naming the static Java methods that mount and unmount per-request identity on the pinned connection: <mount>fqcn#method</mount> and an optional <unmount>. Graphitron reads the method signatures at build time; the mount’s payload parameters become contextArguments on the generated factory, and its return type is the handle later passed to unmount (and readable in services via the $session argMapping sigil). A malformed reference or an <unmount> without a <mount> fails the build. Omit to mount no identity. See Session identity.

tenantColumn

String (database column name)

(none)

Name of the database column that carries the tenant id in a database-per-tenant deployment, matched against catalog columns the way column lookups already match (Java name first, then SQL name, both case-insensitive). Configuring it classifies every catalog table as tenant-scoped (carries the column) or global (does not); the tenant Java type is read off the jOOQ catalog’s column type, and all carrying tables must agree on that type or the build fails. A configured column no table carries also fails the build, because silently classifying every table as global would route tenant data through the default connection. Omit for single-tenant builds; none of the tenant machinery exists then.

devDatabase

DevDatabaseBinding

(none)

Dev database for the MCP execute tool (dev goal only). A <devDatabase> block carrying plain connection coordinates: <url>, <user>, <password>, <dialect> (explicit and enumerated, POSTGRES or ORACLE, never defaulted), the <claims> payload handed to the <sessionState> mount method (inline, or @/path/to/file; the file is re-read on every call), and the <allowClaimsOverride> opt-in for a per-call claims argument (default off). Environment variables override the POM on every field (GRAPHITRON_DEV_DB_URL, GRAPHITRON_DEV_DB_USER, GRAPHITRON_DEV_DB_PASSWORD, GRAPHITRON_DEV_DB_DIALECT, GRAPHITRON_DEV_CLAIMS, GRAPHITRON_DEV_DB_ALLOW_CLAIMS_OVERRIDE), so credentials stay out of the checked-in file. With no url from either source the execute tool is simply absent and every other dev tool works without it; a url with a missing or unsupported dialect fails the goal. See Run a query without an app server.

storeConsole

StoreConsoleBinding

(none)

Read-only SQL console onto the dev session’s own fact store (dev goal only). <enabled> opens it, and the session logs the whole psql command, including the port it bound. The port is ephemeral unless you pin one with <port>; pin one only for a stable connect line, and expect a pinned port to collide when you run more than one dev session. GRAPHITRON_DEV_STORE_CONSOLE and GRAPHITRON_DEV_STORE_CONSOLE_PORT override the POM. The listener is confined to 127.0.0.1, and the console refuses to start if it cannot verify that, so it is never reachable from another machine. Writes to the store’s relations are refused, which catches a slip rather than sandboxing the client. Absent or disabled, no port is bound and the session is unchanged, and the log says how to turn it on. See Query the fact store while the session runs.

dev-goal parameters

Four watch-loop knobs unique to the dev goal, each driven from a CLI property:

Name Type Default CLI property Description

port

int

8487

graphitron.dev.port

TCP port the LSP server binds on 127.0.0.1. The plugin fails fast with a clear message when the port is in use, naming the override property.

debounceMs

long

300

graphitron.dev.debounceMs

Debounce window for both the schema watcher and the classpath watcher. Multiple .graphqls writes inside the window collapse into one regenerate; the same applies to .class changes for the catalog rebuild. Lower for snappier response, higher for noisy editors.

skipInitial

boolean

false

graphitron.dev.skipInitial

Skip the initial generator pass at startup. Useful when the consumer has just run mvn graphitron:generate and the generated sources are already on disk; the dev loop still re-runs on every subsequent save.

compile

boolean

true

graphitron.dev.compile

Compile the generated sources in-process into target/graphitron-classes/<outputPackage>/ (a Graphitron-exclusive directory), incrementally on every save. This runnable image is what the in-process MCP query tools execute against. Set to false to fall back to generate-only behaviour (giving up the query tools). No fail-fast: because the output directory is Graphitron’s alone, any misconfiguration degrades to generate-only rather than corrupting bytecode. See Compiled generated classes.

The CLI properties take precedence over <configuration> values, so mvn graphitron:dev -Dgraphitron.dev.port=9090 works without editing the POM.

Quarkus dev-mode interaction

Consumers running their app under quarkus:dev alongside graphitron:dev may find that schema edits regenerate sources but the running app keeps serving the old GraphQLSchema until they press s in the Quarkus console. The cause is Quarkus’s bytecode-instrumentation hot-swap: it sees the regenerated .java files, decides the changes are method-body-compatible, and replaces classes in place rather than restarting. The schema bean is built once at startup, so a hot-swap doesn’t rebuild it. The matching log line is Files changed but restart not needed - notified extensions: Quarkus saw the change, asked its extensions, and none claimed it as restart-worthy.

Set quarkus.live-reload.instrumentation=false in application.properties to disable instrumentation hot-swap entirely. Every change triggers a full restart, including the schema rebuild. The cost is losing hot-swap on hand-edited Java; for a schema-driven app that is usually the right trade. Putting the .graphqls files on a watched path does not help on its own — Quarkus already watches src/main/resources/, but a change there is still classified by extension claims, and no shipped Quarkus extension claims .graphqls as restart-worthy.

<schemaInput> binding

Each <schemaInput> carries three optional children. The pattern is the only one most consumers need:

Child Type Description

pattern

String (Ant-style glob)

Path or Ant glob relative to the project basedir, expanding to one or more .graphqls files. A literal path matches one file; a glob like src/main/resources/graphql/*/.graphqls matches every schema file under the directory. Both shapes fan into the same loader.

tag

String

Optional tag applied to every type, field, argument, and enum value defined in matched files. Emitted as a @tag(name: "…​") directive on each element. The plugin treats this as an implicit Federation 2 opt-in: when set without an @link in the SDL, the plugin synthesises one with import: ["@tag"]; when set with an @link whose import list omits "@tag", the build fails with a fatal error pointing at the @link. How-to: Apollo Federation transport covers the federation interaction.

descriptionNote

String

Optional sentence appended to every element’s description. Useful for marking a subgraph’s elements with a provenance hint (e.g. "From the orders subgraph") without editing the SDL by hand.

The minimum useful entry is one pattern:

<schemaInputs>
    <schemaInput>
        <pattern>src/main/resources/graphql/schema.graphqls</pattern>
    </schemaInput>
</schemaInputs>

Silencing lint warnings

Graphitron’s schema linter reports style and convention warnings (naming, missing descriptions, deprecated-directive usage, and so on). Every rule is on by default. To silence a rule you disagree with, or to exclude types you cannot change, add a <lint> block to the plugin configuration:

<configuration>
  <lint>
    <!-- Turn a rule off everywhere, by its rule id. -->
    <disabledRules>
      <rule>input-object-name-suffix</rule>
      <rule>types-and-fields-have-descriptions</rule>
    </disabledRules>
    <!-- Skip linting types whose name matches a pattern (glob). -->
    <excludedTypes>
      <type>Legacy*</type>
    </excludedTypes>
  </lint>
</configuration>

Rule ids are the kebab-case names shown in each warning and in the graphitron:diagnostics MCP tool. A misspelled rule id fails the build with the list of valid ids.

disabledRules silences a rule everywhere it would fire: the build log, your editor’s squiggles, and the MCP diagnostics tool. excludedTypes skips the schema linter’s checks on the matching types (glob syntax: * matches any run of characters, ? one character). A handful of advisories come from graphitron’s schema classifier rather than the linter (for example redundant-record-directive); these are not tied to a single type name, so they are silenced by rule id in disabledRules, not by excludedTypes.

The <lint> block carries two list children:

Child Type Description

disabledRules

List<String> (<rule> entries)

Rule ids to silence everywhere. Each is validated against the built-in rule set at build start; an unknown id fails the build.

excludedTypes

List<String> (<type> entries)

Type-name globs whose matching types the SDL lint engine skips. Engine-scoped: a classifier advisory on an excluded type still fires and must be silenced through disabledRules.

Session identity

When you run the owned-connection engine (runtime.newGraphQL(schema)), graphitron takes one database connection per GraphQL operation, when a field first needs it. A <sessionState> block names how per-request identity is mounted on that connection at acquisition and unmounted at release, so every generated query and mutation runs under the caller’s identity without any per-fetcher wiring. Omit the block and no identity is mounted.

Mounting identity from your own code

When identity lives behind a database routine, you usually already have the method: jOOQ generates an executing method for every routine into your Routines class. Name it. mount runs on each connection graphitron takes, before any SQL on it; unmount runs when that connection goes back to the pool:

<sessionState>
  <mount>com.example.db.Routines#connect</mount>
  <unmount>com.example.db.Routines#disconnect</unmount>
</sessionState>

Graphitron reads the signatures at build time: everything but the Configuration parameter is the payload, and the return type is the handle later passed to unmount. Each payload parameter becomes an ordinary contextArgument on the generated factory, so passing the wrong shape is a compile error rather than a cast failure inside the database. Compile the module that owns the named class with -parameters so the parameter names are real (graphitron fails the build naming the method if they are missing); the factory parameter names are the method’s own, jOOQ’s pClaims-style names on a generated routine method, yours on a facade. If a service method also needs the same value, declare it as a contextArgument with the same name and type; it is the same fact, supplied once, not two separately-maintained copies:

var input = Graphitron.newOwnedExecutionInput(claims, userId).query(query).build();

When the call needs massaging, a procedure with several OUT parameters, a mount that combines two calls, a payload reshaped before binding, write a static method of the same shape and name that instead:

public final class KernelIdentity {
    public static SessionHandleRecord mount(Configuration cfg, ClaimsRecord claims) {
        var call = new Connect();
        call.setPClaims(claims);
        call.execute(cfg);
        return call.getPHandle();
    }
    public static void unmount(Configuration cfg, SessionHandleRecord handle) { ... }
}

Consuming the handle in services

Your services can consume the identity your mount resolved. Bind a parameter to mount’s return value with the `$session sigil, and the parameter receives the handle from the connection the call runs on:

access: [Entitlement] @service(
  service: { className: "com.example.AccessService", method: "entitlements" },
  argMapping: "identity: $session")

Under tenant fan-out each call sees its own tenant’s handle. The parameter’s type must match your `mount’s return type; both are your declarations, checked at build time.

One check has to wait until runtime, because the build cannot see which factory an operation was built with: a $session field executed through the escape hatch (Graphitron.newExecutionInput(dsl, …​) with Graphitron.newGraphQL()) runs on a connection graphitron never mounted, so the read fails the request with an error naming the field and the owned entry points rather than handing your service a null where identity belongs.

The seam parameter

The Configuration you are handed is bound to the connection your mount is mounting and carries the jOOQ Settings you gave the runtime, so a schema mapping you configured applies to your routine call as it does to graphitron’s own SQL. It carries no transaction provider, because both methods run outside any transaction, on a connection graphitron holds in autocommit. Declare a Connection parameter instead if you want raw JDBC. A throwing mount evicts the connection rather than returning it to the pool, and fails the request before any of your query’s SQL runs.

When your methods run, and what your mount must mount

Graphitron takes a connection when a field first needs one, mounts identity on it, holds it for the rest of the request, and unmounts before returning it to the pool. mount runs with autocommit on and no transaction open, so what it sets is committed session state: a mutation field’s transaction committing or rolling back later cannot revert it, and graphitron does not re-run your methods around those boundaries. What this asks of you is that identity be session-scoped in your database’s own vocabulary, session GUCs on Postgres, a session or global application context on Oracle. Transaction-scoped storage will not work, and will not work from the first query rather than failing later: set_config(…​, true) and SET LOCAL revert at the end of the statement that set them, and rows in an ON COMMIT DELETE ROWS temp table are deleted by the mount’s own implicit commit. One deployment requirement follows: put graphitron in front of a pool that hands out sessions, not a transaction-pooling proxy such as pgbouncer in transaction mode, which resets session state between transactions and would leave your identity on a backend nobody reads.

<unmount> is optional

If your mount establishes identity wholesale rather than adding to whatever the connection already carried, the next request’s mount overwrites the last one’s, nothing can read stale identity, and unmount only costs you a round trip per request. Omit it. Keep an unmount when something other than graphitron reads scoped data on the same pool, your own DAO code, a migration tool, an admin script, because those readers never mount and would inherit the previous caller’s identity.

If you have no routine at all

Setting a session variable that your RLS policies read is the same shape, written out:

public final class Identity {
    public static void mount(Configuration cfg, String tenantId) {
        cfg.dsl().execute("select set_config('app.tenant', ?, false)", tenantId);
    }
    public static void unmount(Configuration cfg) {
        cfg.dsl().execute("select set_config('app.tenant', '', false)");
    }
}

Note what the unmount clears to: the empty string, not NULL. Postgres cannot restore a touched session variable to unset, so a policy that distinguishes "never set" from "cleared" would silently diverge between a fresh connection and a recycled one. Your RLS policies must treat both NULL and the empty string as "no identity" to stay fail-closed, for example with NULLIF(current_setting('app.tenant', true), '') IS NULL.

Be aware of what this does and does not give you. A session variable is a convention: any SQL running on that connection can overwrite it, including a @service method of your own. If you need identity that consumer code on the same connection cannot forge, mount it with a definer-rights routine, or pass a signed token your mount verifies in the database. That choice is yours to make and graphitron cannot check it for you, which is why it is explained in the security guide rather than warned about at build time.

validate-goal sentinel

The validate goal exists so consumers can run mvn graphitron:validate from the command line without editing their POM’s <execution> block to thread <outputPackage> / <jooqPackage>. The mojo substitutes an inert sentinel (validation.unused) for both packages when they are not configured, so the classifier stage still type-checks against `RewriteContext’s non-null contract. The sentinel never reaches generated code; the validate pipeline emits nothing.

If the parameters are configured (e.g. when validate is bound to the validate lifecycle phase alongside a generate execution), the configured values flow through normally; the sentinel only activates when the parameters are absent.

Putting it together

The canonical generate-and-deploy <execution> against the example schema:

<plugin>
    <groupId>no.sikt</groupId>
    <artifactId>graphitron-maven-plugin</artifactId>
    <version>${graphitron.version}</version>
    <executions>
        <execution>
            <id>rewrite-generate</id>
            <goals><goal>generate</goal></goals>
            <configuration>
                <schemaInputs>
                    <schemaInput>
                        <pattern>src/main/resources/graphql/schema.graphqls</pattern>
                    </schemaInput>
                </schemaInputs>
                <jooqPackage>com.example.jooq</jooqPackage>
                <outputPackage>com.example.generated</outputPackage>
            </configuration>
        </execution>
    </executions>
</plugin>

For the dev loop add a second execution bound to no phase, or invoke mvn graphitron:dev directly. graphitron-sakila-example’s pre-wired POM (`graphitron-sakila-example/pom.xml) is the canonical worked example.

Codegen classpath

The reflection sites that resolve consumer-declared classes (@service / @externalField target classes, the classes a type’s backing record is reflected from off its producing field, @condition resolvers, the generated jOOQ DefaultCatalog) load from the project’s compile classpath plus every reactor sibling’s target/classes, not from the plugin realm. Any artifact declared as a normal <dependency> of the consumer module is visible to the generator at codegen time. A class an author names in the schema must come from that declared set: the generator rejects a schema naming a class that reaches the classpath only transitively or only through a <plugin><dependencies> block, naming the coordinate to declare; see Make the class nameable. Do not mirror service or catalog jars under <plugin><dependencies>; that route is withdrawn.

Drift protection

A bidirectional coverage test (MojoDocCoverageTest in graphitron-maven-plugin) asserts every @Parameter-annotated field on AbstractRewriteMojo, GenerateMojo, ValidateMojo, and DevMojo has a row in this page’s parameter tables, and every parameter row corresponds to an annotated field on one of those Mojos. A new @Parameter field cannot land green without a doc row; a removed field forces the row’s removal.

See also

  • Runtime API reference covers the Graphitron facade and GraphitronContext interface this plugin’s generate goal emits into the consumer module.

  • Directive reference covers the SDL surface every <schemaInput> parses against, including the four directives that take external Java code through ExternalCodeReference.

  • Tutorial page 1: Prerequisites is the first place a consumer meets the plugin and the example consumer’s pre-wired POM.