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 three of its own. Two of the parameters (<schemaInputs> and <namedReferences>) are list-of-complex-objects with their own POM bindings.
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 |
|---|---|---|---|
|
|
Run the rewrite pipeline (load → attribute → classify → validate → emit) and write Java sources under |
|
|
|
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) |
|
(none; CLI-only) |
Run the LSP server on |
|
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 |
|---|---|---|---|
|
|
|
Directory the |
|
|
(none) |
Root Java package the generator writes under. Required for |
|
|
(none) |
Root Java package of the consumer’s jOOQ-generated catalog (the |
|
|
empty |
List of |
|
|
empty |
Reusable |
|
|
|
File-name suffixes that count as GraphQL schema files. Drives three places at once: the |
|
|
(none) |
Lint suppression. A |
|
|
(none) |
Session identity (R429). A |
|
|
(none) |
Dev database for the MCP |
dev-goal parameters
Three watch-loop knobs unique to the dev goal, each driven from a CLI property:
| Name | Type | Default | CLI property | Description |
|---|---|---|---|---|
|
|
|
|
TCP port the LSP server binds on |
|
|
|
|
Debounce window for both the schema watcher and the classpath watcher. Multiple |
|
|
|
|
Skip the initial generator pass at startup. Useful when the consumer has just run |
|
|
|
|
Compile the generated sources in-process into |
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 |
|---|---|---|
|
|
Path or Ant glob relative to the project basedir, expanding to one or more |
|
|
Optional tag applied to every type, field, argument, and enum value defined in matched files. Emitted as a |
|
|
Optional sentence appended to every element’s description. Useful for marking a subgraph’s elements with a provenance hint (e.g. |
The minimum useful entry is one pattern:
<schemaInputs>
<schemaInput>
<pattern>src/main/resources/graphql/schema.graphqls</pattern>
</schemaInput>
</schemaInputs>
<namedReference> binding
Each <namedReference> carries two required children:
| Child | Type | Description |
|---|---|---|
|
|
Lookup key the SDL uses to refer to this reference. Matched against the |
|
|
Fully qualified Java class name the reference resolves to. The classifier checks this against the consumer module’s classpath and fails the build with the offending name if the class can’t be resolved. |
<namedReferences> is empty by default; consumers populate it when the same external class is referenced from many places and the FQCN repetition becomes painful. The directive-site form (without name:) takes a literal className, so this binding is purely an indirection and never adds new capability.
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 |
|---|---|---|
|
|
Rule ids to silence everywhere. Each is validated against the built-in rule set at build start; an unknown id fails the build. |
|
|
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 |
Session identity
When you run the owned-connection engine (runtime.newGraphQL(schema)), graphitron pins one database connection per GraphQL operation. A <sessionState> block tells it how to mount per-request identity on that connection, at acquisition, and unmount it 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.
There are two forms; configure one, not both.
Function-hook form
Name consumer-authored database callables. connect mounts identity from the opaque claims payload (typically the request’s JWT); disconnect unmounts it. This is the form to use on Oracle, and whenever identity lives behind a privilege fence (a definer-rights package, VPD, RAS):
<configuration>
<sessionState>
<connect> <!-- (p_claims IN, p_handle OUT) -->
<call>Pk_Ras.Connect</call>
<handle>true</handle>
</connect>
<disconnect> <!-- (p_handle IN) -->
<call>Pk_Ras.Disconnect</call>
<handle>true</handle>
</disconnect>
</sessionState>
</configuration>
<handle>true</handle> declares that connect returns an opaque OUT handle (for example a RAS session id) that disconnect is later called with. It must be declared on both sides or neither; a handle produced by one side and not bound by the other fails the build. Everything domain-shaped, claim parsing, entitlement filtering, session management, lives inside the callables, in the database’s own language; graphitron only guarantees the pair runs at mount and unmount.
Your callables must set and reset session-scoped connection state, never transaction-scoped state. On Postgres use set_config(key, value, false), never SET LOCAL or set_config(…, true); Oracle RAS attach/detach and VPD context calls are session operations and already comply. Neither callable may rely on a surrounding transaction committing or rolling back, and both may assume graphitron invokes them outside any open transaction: acquisition normalizes the connection to autocommit before connect, and release settles any transaction the operation left open before disconnect. See the hook state contract for why a transaction-scoped mount is a security defect over a pooled connection.
Identity that mounts must unmount, so a <connect> without a <disconnect> fails the build. A genuinely unmount-free design opts out explicitly with an empty <disconnect/>:
<sessionState>
<connect><call>Pk.SetContext</call></connect>
<disconnect/> <!-- explicit unmount-free opt-out -->
</sessionState>
Transaction survival and the settle re-fire
Graphitron cannot see whether the state your connect callable mounts actually survives a transaction commit or rollback (identity parked in commit-sensitive storage would not). By default it therefore assumes it may not, and re-fires the pair, disconnect with the old handle, connect capturing a fresh one, after each top-level mutation-field transaction settles, so the field’s payload read-back and later mutation fields always see mounted identity. Query operations run in autocommit and never settle, so this safe default costs nothing on the read path; what it costs per mutation field is one extra pair of callable round trips riding the field’s commit.
If your callables mount plain session-scoped state (RAS sessions, VPD contexts, session variables), declare it and keep acquisition-scoped mounting with no re-fire:
<sessionState>
<connect><call>Pk_Ras.Connect</call><handle>true</handle></connect>
<disconnect><call>Pk_Ras.Disconnect</call><handle>true</handle></disconnect>
<stateSurvivesTransactions>true</stateSurvivesTransactions>
</sessionState>
The declaration is a statement about your database code that graphitron trusts; declare it only when the mounted state is genuinely session-scoped (see the hook state contract). It applies only to a paired function hook: declaring it with the <variables> sugar fails the build (the sugar survives settles structurally and needs no declaration), as does declaring it with the empty <disconnect/> opt-out (an unmount-free hook has no pair to re-fire).
Postgres <variables> sugar
For the common Postgres case, graphitron generates both hook halves from a list of session variables, so you write no SQL:
<sessionState>
<variables>
<variable>
<name>app.user_id</name>
<claim>sub</claim>
</variable>
<variable>
<name>app.tenant</name>
<claim>tenant</claim>
</variable>
</variables>
</sessionState>
connect sets each variable from the named claim in the payload JSON in a single round trip (set_config('app.user_id', …→>'sub', false)); disconnect clears exactly those variables. Both halves come from the one variable list, so they cannot drift.
| Child | Type | Description |
|---|---|---|
|
|
The Postgres session variable (GUC) to set, for example |
|
|
The claim key read from the payload JSON, for example |
A claim absent from the payload sets the variable to the empty string, and disconnect clears each variable to the empty string rather than to NULL (Postgres cannot restore a touched session variable to unset). Your RLS policies must therefore treat both NULL and the empty string as "no identity" to stay fail-closed, for example with NULLIF(current_setting('app.user_id', true), '') IS NULL.
The sugar generates PostgreSQL. If you build the runtime with a non-Postgres dialect, Graphitron.runtime(dataSource, dialect) fails immediately with a clear message rather than at the first request; use the function-hook form for other dialects.
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 / @tableMethod 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 already declared as a normal <dependency> of the consumer module is visible to the generator at codegen time; consumers do not need to mirror service / catalog jars under <plugin><dependencies>. The rare legitimate case where <plugin><dependencies> still makes sense is pinning a service or catalog jar to a different version from the project’s compile dep ; the plugin realm parents the codegen loader, so an override under <plugin><dependencies> still wins through the parent chain.
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
Graphitronfacade andGraphitronContextinterface this plugin’sgenerategoal emits into the consumer module. -
Directive reference covers the SDL surface every
<schemaInput>parses against;<namedReference>entries matchExternalCodeReference.name:on the four directives that take external Java code. -
Tutorial page 1: Prerequisites is the first place a consumer meets the plugin and the example consumer’s pre-wired POM.