A 30-second mental model of the pipeline that turns your schema and your jOOQ catalog into the resolvers your application serves at runtime. Aimed at the user, not the contributor; for the contributor-level pipeline detail see Code Generation Triggers.
The three moving parts
-
Your GraphQL schema with directives. The directive reference catalogues the vocabulary; the how-to chapter pairs each directive with a recipe.
-
Your jOOQ catalog. Generated by jOOQ’s own
mvnplugin from the database; this is the typed view of the schema Graphitron resolves directives against. -
Graphitron’s Maven plugin. Reads (1), looks up identifiers in (2), and emits Java sources. The Mojo configuration reference covers the goals (
generate,validate,dev) and every parameter.
The build is mechanical: the generator does not query the database at build time, and runtime queries do not re-read the GraphQL schema or the directives. The schema and the directives are both parsed once, classified once, and the generators emit code from the classified result. This is the property that makes the build deterministic and the runtime cheap.
What happens at build time
Graphitron’s plugin runs after jOOQ codegen and before your application’s compile step:
-
Parse. The plugin reads your
.graphqlsfiles and the rewrite’s auto-injected directive definitions. Parse errors fail the build withfile:line:col: error: …messages naming the offending file. -
Classify. Every type and every field is sorted into a category: a
@table-bound type produces SQL; a@record-bound type carries a backing class; a field with@lookupKeybecomes a batched lookup; a field with@asConnectionbecomes a Relay connection; and so on. The classifier mental model explains what the categories are and why. -
Validate. The validator cross-checks the classified result against the jOOQ catalog and against structural invariants (no circular types, no
@asConnection+@lookupKeytogether, every@field(name:)resolves to an actual column). Rejections come out as a typed surface; the diagnostics glossary catalogues every closed-set rejection kind. -
Emit. For each classified field, the generator writes a
DataFetcher(or aTypeResolver, or a row-method on a service interface) into your output package. The emitted code is plain Java that calls into jOOQ at runtime; you can read it like hand-written code.
If anything in step 2 or 3 fails, the build stops with a structured rejection. The catch-all is the validator’s stub-arm: a feature that classifies but cannot be emitted yet falls into a runtime stub with a planned-feature pointer instead of a generated body. The deferred rejection kind is what you see at validate time when this happens.
What happens at request time
Your application starts with Graphitron.buildSchema(…) (Runtime API reference), which assembles the wired GraphQLSchema once and hands it back. From then on, each GraphQL request:
-
Lands in graphql-java. The engine parses the operation, plans an execution, and walks fields top-down.
-
Hits a Graphitron
DataFetcher. For each field that Graphitron generated against, the fetcher Graphitron emitted is invoked with the standardDataFetchingEnvironment. -
Calls into jOOQ. The fetcher reads the per-request
DSLContextoff theGraphQLContextthatGraphitron.newExecutionInput(dsl, …)populated, builds a typed query (selection-aware: only the columns the client asked for), and executes it. -
Batches and projects. DataLoader-backed fields collect their keys per request and execute one query per batch instead of one per parent row. The batching model explains the mechanics and the contract.
-
Returns the projected shape. The query result is mapped onto the GraphQL response shape; lists, nulls, connections, and federation
_entitiesdispatch all flow through the same return path.
There is no runtime introspection of directives, no schema reading, no reflection of jOOQ tables. Everything that matters at runtime was decided at build time, and the runtime path is the one the generator emitted into your sources.
Where to look when something is off
-
A directive doesn’t bind. Build error with a candidate hint. The diagnostics glossary explains the rejection kinds; the relevant directive page in the directive reference gives the binding rules.
-
A query is slow. Read the generated
*Fetchers.javafor the path. The query is selection-aware, so missing columns mean missing GraphQL selections; an unexpected join means an explicit@referencesomewhere up the path. -
N+1 in the logs. The field is not batched. Either it has no
@splitQuery/@lookupKeywhen it should, or its parent isn’t part of a batch. The batching model is the reading path. -
A feature returns a stub. Validator
deferredrejection at build time, runtime stub at request time. Check the rejection’splanSlug:for the roadmap entry that owns the feature.
See also
-
Why database-first frames the architectural stance.
-
Why jOOQ and GraphQL-Java frames the dependency triple.
-
Classifier mental model explains what the build’s classification step does.
-
Batching model covers DataLoader wiring at request time.
-
Design decisions answers user-visible "why does it work that way" questions.