The rewrite generator (graphitron-rewrite) is directive-compatible with the legacy generator (graphitron-codegen-parent): the same @table, @field, @service, @reference, @asConnection, @nodeId, @condition directives all do approximately the same thing on the rewrite, and a schema authored against the legacy generator’s directive surface will mostly compile under the rewrite without changes. The "mostly" is what this recipe covers: the hard removals (build fails until you remove the directive, or, in one case, resolves differently with no error at all), the locations that still warn today and will fail later, the deprecated arguments (build succeeds with a deprecation notice from graphql-java), and the one behavior divergence the rewrite intentionally preserves over legacy semantics.
|
Names from inside the generator. This page names types from graphitron’s own classification model. They are accurate today, and worth knowing when a rejection message quotes one back at you, but they are not part of the authoring contract: the classification walk that produces them is being drained, and these names retire with it. What stays stable is the schema you write, the directive reference that describes it, and the diagnostics glossary. |
This recipe is organised by what changes for you, not by which directive each topic touches. Run through the sections in order; each lands a class of migration steps before the next one introduces a new shape.
Authoritative supported surface
The rewrite’s directive surface and the schema shapes its classifier emits are both regenerated from the test suite. The two lists below are mechanically derived: every directive listed under "Supported directives" is declared in the rewrite’s directives.graphqls, and every shape under "Supported schema shapes" has at least one execution-tier or pipeline-tier fixture that exercises it through end-to-end classification.
A schema using only the directives and shapes named below should migrate cleanly. The remaining sections of this recipe cover the mechanical rewrites the legacy → rewrite migration requires.
Supported directives
The rewrite generator declares and supports the following directives. Each is documented in the architecture chapter:
-
@asConnection -
@asFacet -
@condition -
@defaultOrder -
@discriminate -
@discriminator -
@enum -
@error -
@externalField -
@field -
@index -
@lookupKey -
@mutation -
@node -
@nodeId -
@order -
@orderBy -
@pivot -
@record -
@reference -
@referenceFor -
@routine -
@scalarType -
@service -
@splitQuery -
@table -
@tenantFanOut
Removed / rejected directives
These directives are still declared so existing schemas parse, but the rewrite no longer supports them: applying one is a build error. Remove them from your schema (see the migration guide for replacements):
-
@multitableReference -
@notGenerated
Legacy-only directives
These directives existed in legacy graphitron but are removed in the rewrite. Drop them from your schema (or replace per the notes below) before migrating:
-
@experimental_procedureCall -
@tableMethod
Argument-shape changes
The following directives have argument-shape changes between legacy and rewrite. Consumers must update existing usages:
@externalField
-
+ reference: ExternalCodeReference! (rewrite only)
@field
-
- javaName: String (legacy only)
@mutation
-
+ multiRow: Boolean = false (rewrite only)
-
+ table: String (rewrite only)
Supported schema shapes are enumerated below by output sealed-leaf class. Each supported entry has at least one execution-tier or pipeline-tier fixture in the rewrite test suite.
GraphitronField
-
UnclassifiedField: A field that could not be classified. A schema containing unclassified fields is invalid —
RootField
-
DmlTableField: The direct-return DML mutation:
@mutation(typeName:)on a field returning the -
MutationBulkDmlRecordField: A record-returning DML mutation with bulk DML input and a list-shaped
-
MutationDmlRecordField: A record-returning DML mutation: the schema field carries
-
MutationRoutineWriteField: A mutation field whose table chain starts with a database routine: the routine call
-
MutationRoutineWriteRecordField: A mutation field whose write is a hop-less
@routinecall and whose return is a -
MutationServicePolymorphicField: A mutation field backed by a developer-provided service method that returns a multitable
-
MutationServiceRecordField: A mutation field backed by a developer-provided service method, returning a non-table type
-
MutationServiceTableField: A mutation field backed by a developer-provided service method, returning a table-mapped type
-
MutationServiceTableInterfaceField: The mutation analogue of
QueryField.QueryServiceTableInterfaceField: a root -
QueryInterfaceField: A root query field returning a multi-table
GraphitronType.InterfaceType -
QueryNodeField
-
QueryNodesField
-
QueryServicePolymorphicField: A root query field backed by a developer-provided service method that returns a multitable
-
QueryServiceRecordField: A root query field backed by a developer-provided service method, returning a non-table type
-
QueryServiceTableField: A root query field backed by a developer-provided service method, returning a table-mapped type
-
QueryServiceTableInterfaceField: A root
@servicefield returning a single-table discriminated interface -
QueryTableField: A root table read whose FROM starts at the return type’s own table or a routine chain
-
QueryTableInterfaceField
-
QueryUnionField: A root query field returning a multi-table
GraphitronType.UnionType
ChildField
-
BatchedInterfaceField: A list- or connection-cardinality child field returning a multi-table
-
BatchedPivotField: The batched (
@splitQuery)@pivotleaf: the same projection as -
BatchedTableField: A DataLoader-batched keyed re-query anchor: the field launches its own SELECT keyed on a
-
BatchedTableInterfaceField: The DataLoader-batched half of the
TableInterfaceFielddelivery split: one keyed -
BatchedUnionField: A list- or connection-cardinality child field returning a multi-table
-
ColumnBackedField: A column-backed output carrier on a table-backed parent: the field’s value is produced
-
ColumnBackedReferenceField: A column-backed output carrier on a table-backed parent reached through a
-
ComputedField: A child field using
@externalField: the developer provides a static method -
ErrorsField: The
errorsfield on a payload type. Lift target for the payload-side of a -
InterfaceField: A single-cardinality child field returning a multi-table
-
NestingField: A nesting child field whose value is a fragment of the parent’s table-bound projection
-
ParticipantColumnReferenceField: A scalar field on a
GraphitronType.TableInterfaceTypeparticipant that -
PivotField: The inline
@pivotleaf: a discriminator-keyed aggregate projection folded into the -
(not yet supported) PivotSlotField: One projection slot of a
@pivotfield’s return type, carrying exactly one fact: its -
RecordCompositeField: The single data field on an
@servicerecord-composite carrier payload. The -
RecordReadField: A value read off a record-backed parent’s in-memory source object: the scalar / enum /
-
ServiceRecordField: A child field backed by a developer-provided service method (
@service), where the -
ServiceTableField: A child field backed by a developer-provided service method (
@service), where the -
SingleRecordIdField: The single data field on an
@servicesource-record carrier: an ID-typed scalar -
SingleRecordIdFieldFromReturning: The single data field on a payload-returning DELETE carrier: an ID-typed scalar encoding
-
TableField
-
TableInterfaceField: A child field returning a
GraphitronType.TableInterfaceType(@table -
UnionField: A single-cardinality child field returning a multi-table
InputField
-
ColumnBackedField: An input field successfully resolved to one or more SQL columns on the consuming
-
ColumnBackedReferenceField: An input field that uses
@reference(or an -
ConditionOwnedField: Input field whose explicit
@condition(override: true)method owns the WHERE -
NestingField: An input field whose GraphQL type is itself an input
-
UnboundField: Input field that does not bind to a SQL column: the classifier looked
GraphitronType
-
Backed: A payload carrier whose backing class was reflected from its
-
ConnectionType: A Relay connection object type — the outer wrapper whose fields are
-
EdgeType: A Relay edge object type — the inner wrapper whose fields are
cursor: String!and -
EnumType: A GraphQL enum type, recorded so
schema.types()is complete for emission -
ErrorType: An object type annotated with
@error. Maps Java exceptions to GraphQL error responses -
FacetValueType: A synthesised, cross-schema-reusable facet value type, e.g
-
FacetsType: The synthesised per-connection facets container, e.g
-
InterfaceType: An interface with no directives. Participating types may be table-bound (
ParticipantRef.TableBound) -
JavaRecordInputType: A non-table input type backed by a Java
recordclass -
JavaRecordType: A result type whose producer’s reflected return is a Java
recordclass -
JooqRecordInputType: A non-table input type backed by a jOOQ
Record<?>(not table-bound) -
JooqRecordType: A result type whose producer’s reflected return is a jOOQ
Record<?>(not table-bound) -
JooqTableRecordInputType: A non-table input type backed by a jOOQ
TableRecord<?> -
JooqTableRecordType: A result type whose runtime carrier is a jOOQ record of a resolved table
-
NestingType: A nesting projection: a directiveless SDL object type embedded under a
@table-bound -
NodeType: A GraphQL object type carrying
@tablethat has published the RelayNode -
PageInfoType: The Relay
PageInfoobject type. Exactly one instance per schema; the classifier -
PojoInputType: A non-table input type backed by a plain Java class (POJO), or one for which no producer
-
RootType: A root operation type (Query or Mutation). Unmapped — no source context, no SQL until
-
ScalarType: A GraphQL scalar type whose Java type and
GraphQLScalarTypeconstant (or inline -
TableInterfaceType: An interface annotated with
@tableand@discriminate, where implementing -
TableType: A GraphQL object type annotated with
@table, without@node -
UnclassifiedType: A type that could not be classified — examples include an unresolvable
@table -
UnionType: A union type whose member types all have
@table
One replacement pointer for the "Legacy-only directives" list above: legacy @tableMethod bound a consumer-authored static method returning a jOOQ table. The rewrite does not declare it. Where the method wrapped a database function, use @routine, which binds the generated Routines-class call directly; where it applied a filter, express that with @condition.
No migration needed: bare id on a node type
No migration needed, and this is the one legacy shape that became more supported rather than less. A bare id: ID! on a node type is the Relay Node interface’s own field, and graphitron reads it as that node’s global ID by construction, with no directive and no warning:
# Supported, no directive required
type Customer implements Node @table(name: "customer") @node {
id: ID!
customerId: Int! @field(name: "CUSTOMER_ID")
}
Writing @nodeId on that field is still legal and means exactly the same thing, so existing schemas that spell it out need no edit either. What you cannot write there is @nodeId(typeName:): the enclosing type already answers which node the field identifies, so naming one is either a restatement or a contradiction, and the build rejects the argument rather than checking it.
The implicit reading stops at Node.id. A different bare ID field on the same type (externalId: ID) used to be synthesised into a second global-ID carrier with a WARN; it is an ordinary column-mapped scalar now, so a field with no column of that name fails with column 'externalId' could not be resolved instead of silently encoding. Add @nodeId if you meant a node id, or @field(name: "…") to point at the column.
Hard removals
Two directives the rewrite refuses to accept, one argument it no longer declares, and one inference it no longer performs. The two directives stay declared so the SDL still parses, but every application is rejected at classify time with a "no longer supported" message. The argument is a harder break: the schema does not load at all. The retired inference is quieter than either, and is called out as such below.
@notGenerated
Legacy schemas used it to mark fields the developer would wire by hand through RuntimeWiring; the rewrite requires every field to be fully described by the schema. Any application is rejected at build time:
@notGenerated is no longer supported. Remove the directive; fields must be fully described by the schema.
The directive is still parsed (so the SDL still validates), but every classify-time application fails with that message. The migration is a one-step delete-and-replace: remove the @notGenerated and add the directive that names the actual data source.
| Original site | Rewrite replacement | Notes |
|---|---|---|
|
Wire the resolver as a service method; the rewrite generates the data fetcher and dispatches to your code. See How-to: Handle services for the argument binding and return-shape rules. |
|
|
|
|
|
A typed Java accessor |
The class-backed parent (its backing class reflected from its producer) must expose the data through a typed Java-record accessor (auto-derived |
|
|
Service methods can be batched with |
|
(no replacement needed) |
The legacy "skip interface/union wiring" axis was always tied to |
The @notGenerated reference page carries the same matrix; this recipe expands the right-hand column with cross-links to the recipe each replacement is best documented in.
@multitableReference
Legacy schemas used @multitableReference(routes: […]) to declare per-implementation join paths for a field returning a multi-table interface or union. The rewrite generates multi-table interface dispatch from @discriminate / @discriminator (with @reference for the FK-driven join) and does not need a separate per-route description. Any application is rejected at build time:
@multitableReference is no longer supported. Remove the directive; the rewrite generates multi-table interface dispatch from @discriminate / @discriminator without an explicit multitable-reference path.
Migration is a one-step delete-and-rebuild: drop the @multitableReference directive (and its ReferencesForType argument tree) and model the multi-table dispatch through @discriminate / @discriminator. How-to: Polymorphic types covers the supported shapes.
name: on ExternalCodeReference
Unlike the two directives above, this one is not even parsed: the argument is undeclared, so the schema fails to load before classification runs.
# Legacy form, no longer supported
@service(service: {name: "SampleQueryService", method: "filmsByService"})
Replaced with className::
# Canonical form
@service(service: {className: "no.sikt.graphitron.rewrite.test.services.SampleQueryService", method: "filmsByService"})
className: takes the FQCN. The legacy name: was a short name resolved through the plugin’s externalReferences: config; the rewrite briefly carried the argument as a deprecated alias resolved through its own <namedReferences> parameter, and both are now gone. The failure names the offending field:
uses an illegal value for the argument 'service' on directive 'service'.
Fields ['name'] not present in type 'ExternalCodeReference'.
The migration is mechanical: copy the FQCN from the legacy externalReferences: entry into className: at every site, then delete the plugin config block. This applies to all four directives that take an ExternalCodeReference (@service, @externalField, @condition, @enum), including a @condition nested in a @reference path step.
[ID!] @field(name: "X_ID") on filter input fields
Legacy input types let an ID! (or [ID!]) field carry a column name through @field(name:), and the FK qualifier was inferred from the catalog so the value was decoded as a global ID. That inference is removed: @field(name:) now means what it says everywhere, binding the named column and nothing more, so a field written this way silently becomes an ordinary column filter over the raw value.
This is the one change in this recipe with no build error attached, because both readings resolve. Audit filter inputs whose ID fields carry @field(name:) against an FK column.
Before:
# Decoded a global ID via the FK qualifier. Now binds CUSTOMER_ID as a plain column.
input CustomerFilter {
customerIds: [ID!] @field(name: "CUSTOMER_ID")
}
After:
# Canonical form
input CustomerFilter {
customerIds: [ID!] @nodeId(typeName: "Customer")
}
When the FK is ambiguous (more than one path from the consuming field’s table to the target), add an explicit @reference(path: [{key: "fk_constraint_name"}]) alongside the @nodeId. The @reference recipe covers the FK-disambiguation mechanics; the migration is otherwise mechanical.
An input field named for its target’s own id needs no directive at all: id: ID! on an input consumed against a node-backed table reads as that node’s global ID, the same way Node.id does on the type. Where the table backs more than one node type the build fails and asks for @nodeId(typeName:), rather than picking one.
WARN today, error later
Paths that still work in the rewrite for backwards compatibility but emit a per-occurrence WARN at build time. Plan to migrate them before the retirement gate fires (no committed date).
@table on input types
Legacy schemas bound every mutation and filter input to a table with @table on the input type. The rewrite deprecated that location: an input’s fields resolve against each consuming field’s table, so the input-side declaration is redundant, and an input reused across consumers resolves per-consumer. The directive is accepted and then ignored, with a per-usage build warning, and will be rejected in a future release. @table on OBJECT and INTERFACE is unchanged.
Because it is ignored rather than honoured, an input whose @table named a different table from the one its consuming field resolves does not migrate silently-but-correctly: the declared table is discarded and the consumer’s is used. The warning is the only signal, so read it instead of assuming the two agreed.
The migration is per-consumer:
-
Filters and lookup inputs: delete the
@table; nothing replaces it. The fields resolve against the consuming query field’s return-type table. -
INSERT/UPDATEmutation inputs: delete the@table; the write target is derived from the mutation field’s return type (a@tablereturn, or a payload whose data field is a@tableelement). If the return names no table (an encoded-ID/ scalar return), add@mutation(table: "…")on the field. -
DELETEmutation inputs: delete the@tableand add@mutation(table: "…")on the field (aDELETEcannot derive its table from the return type).
The @table reference page carries the deprecation statement; @mutation documents the write-target derivation.
@table alone no longer auto-promotes to NodeType
Historical only: legacy versions of the rewrite would silently promote any @table SDL type to NodeType if its backing jOOQ class carried NODE_TYPE_ID / NODE_KEY_COLUMNS metadata, even without implements Node. This behavior was retired after a mass typeId collision: ~200 backing types each carrying __NODE_TYPE_ID = "195" were promoted in lockstep and then symmetrically demoted to UnclassifiedType.
The current rule: metadata alone promotes nothing, and implements Node is the opt-in. A @table type that does not declare the interface is a regular TableType no matter what its jOOQ class publishes, which is what keeps a nesting projection over a node-bearing table from becoming a second node. A @table type that does declare implements Node over a table publishing that metadata is a node, and takes typeId and keyColumns from the catalog.
So if a schema relied on the auto-promotion (the type was reachable via Query.node or carried @nodeId fields), add implements Node to the declaration. You need @node as well only when the backing jOOQ class publishes no metadata, or when you want to override a published value; the @node reference covers both. Fields inside the type that depend on node behavior follow the rules in the previous two sections.
One case to expect on this path: if the table also has a column literally named id, the type’s id field is ambiguous between the encoded global ID and that column, and the build fails until you write @nodeId or @field(name: "id") on it. The error names both remedies.
@record is now a no-op
Legacy schemas used @record(record: {className: "…"}) to bind a class-backed SDL type to its Java class. The rewrite now derives that binding by reflection from the type’s producer instead: for an output type, the producing field’s return type (a @service method return or a parent-accessor chain); for an input type, the method parameter the input flows into. The directive still parses (so existing schemas keep loading), but it is ignored.
A reachable type still carrying @record triggers a build WARN telling you to remove it. The WARN distinguishes three cases:
| Case | Meaning |
|---|---|
redundant |
Reflection derives the same class the directive named. Remove the directive; nothing else changes. |
shadowed by |
The type also carries |
disagrees |
Reflection’s class differs from the one the directive named. Reflection wins; the directive is ignored. Remove the directive and verify the producer’s type is the one you want. |
Migration is mechanical: delete the @record(record: {…}) annotation. Make sure the type’s producer (the @service return, or the @externalField lift’s Field<…> element type) is the Java class you intend to back the type. How-to: Result-type variants covers the reflected backing-class shapes; the @notGenerated-on-a-class-backed-parent row above covers the child-fetch directives that remain unchanged.
Deprecated arguments: phase out at your leisure
The directives below still accept these arguments, but graphql-java surfaces a deprecation notice every time the schema is parsed, and the long-term plan is to drop them. Migrate when you next touch the directive.
connectionName: on @asConnection
# Legacy override, deprecated
movies: [Movie!]! @asConnection(connectionName: "SharedMoviesConnection")
otherMovies: [Movie!]! @asConnection(connectionName: "SharedMoviesConnection")
The override coalesces multiple connection-bearing fields onto one synthesised type. The rewrite default produces one Connection type per field (<ParentType><FieldName>Connection); sharing a name across fields conflates distinct semantic contexts (different parents, filters, orders, cursor formats) at the type level.
Migration: drop the argument and let each field own its synthesised connection type. The four reasons for the per-field default and the migration considerations live in How-to: Cursor-paginated connections under "One field, one connection type".
@index on enum values
# Legacy form, deprecated
enum FilmSort {
BY_TITLE @index(name: "IDX_TITLE")
}
Replaced with @order(index:):
# Canonical form
enum FilmSort {
BY_TITLE @order(index: "IDX_TITLE")
}
@order is the unified enum-value sort surface; it accepts index:, fields:, or primaryKey: (exactly one). How-to: Sort results covers the column-source choice. @index still works on enum values today; the deprecation notice at parse time is the migration prompt.
Behavior divergence: @condition cascade
The rewrite intentionally diverges from the legacy generator on one semantic point. Schemas that relied on the legacy reading need to be updated; schemas that didn’t (or that already wrote schemas the rewrite reads correctly) are unaffected.
Legacy reading. @condition(override: true) on an outer field meant "outer owns everything": all inner explicit @condition annotations on the input’s fields were dropped, and the outer method’s predicates ran alone.
Rewrite reading. @condition(override: true) suppresses only the implicit column = ? predicates the rewrite would have emitted on its own. Inner explicit @condition annotations still fire: every @condition you write produces SQL.
The rewrite’s reading is the intended one. Under legacy semantics, an inner @condition was meaningless once any ancestor used override: true, which made schema review brittle (an outer override silently inert-ified inner annotations). The rewrite’s reading is composable: each @condition always fires; only the rewrite’s own implicit predicates are negotiable.
Schemas that relied on the legacy "outer owns everything" silently dropping inner conditions will produce different SQL under the rewrite. The migration is to rewrite the schema so the inner @condition annotations describe what should fire (instead of relying on them being ignored), or to remove them if their absence is the intent.
The example schema’s filmsOuterOverrideFilterInput and filmsOuterOverridePlainInput fixtures pin the rewrite’s behavior as a regression-fence. The full mechanics live in How-to: Stacking and overriding conditions under "Divergence from legacy semantics".
Things that didn’t change
Every other directive on the legacy surface reads under the rewrite the same way, with one nuance per directive worth flagging:
-
@tableonOBJECT/INTERFACE,@field,@reference: unchanged; the catalog-driven mechanics are identical. (@tableon input types is deprecated and ignored; see above.) -
@service,@externalField,@enum: unchanged surface; the only change is thename:→className:migration onExternalCodeReference(above). (@recordis the exception: it is now a no-op; see "`@record` is now a no-op" above.) -
@discriminate/@discriminator: unchanged; the rewrite generates the type resolvers and runtime dispatchers automatically (noRuntimeWiring-side wiring needed). -
@asConnection: unchanged synth-vs-declared shape; onlyconnectionName:is deprecated. The rewrite emits the same Relay connection contract. -
@orderBy,@order,@defaultOrder: unchanged; the rewrite recognises the same shapes. The cursor wire format is the same base64-encoded NUL-separated tuple. -
@node,@nodeId: unchanged contract. The migration work is the@field(name:)inference removal above, plus the two shapes it lists.keyColumns:migration semantics carry over (cursor invalidation on key changes). -
@condition: same surface; the cascade divergence (above) is the only behavior change. -
@error: same handler-tuple surface, anddescription:keeps its Graphitron 9 contract of replacing the exception’s message onmessage:. Two classify-time rejections are new: duplicate handler tuples, anddescription:on a{handler: VALIDATION}entry (the validator emits one error per constraint violation, each with its own interpolated message, so put the client-facing string on the constraint annotation’smessageattribute). How-to: The errors channel covers the dispatch loop and the resolution order formessage:. -
@lookupKey,@splitQuery: unchanged. Mid-migration schemas that mixed@splitQuerywith@notGenerated(the legacy custom-batch-fetcher pattern) need to migrate the body to@service(above).
Migration sequence
The recommended order, from least invasive to most:
-
Migrate
name:toclassName:on everyExternalCodeReferenceinstance and drop the corresponding entries fromexternalReferences:plugin config. Do this first: the argument is undeclared, so the schema does not load at all until it lands, and none of the later steps can be observed before it does. -
Run a
mvn installto surface every@notGeneratedand@multitableReferencerejection, and every@table-on-input warning. Replace each@notGeneratedwith the directive named in the matrix above. The build won’t compile under the rewrite until the two rejections are cleared; the@table-on-input warnings do not block it, but clear them too (per the per-consumer steps above) before the location is rejected in a future release. -
Audit input types and arguments for
ID-typed slots carrying@field(name:)on a foreign-key column; replace with@nodeId(typeName: "T"). Nothing in the build points at these: the inference that used to decode them was removed, so they now bind the raw column and resolve without an error. This is the one step you cannot drive from the build output. -
Drop
connectionName:arguments from@asConnectionand let each field own its connection type. -
Migrate
@indexto@order(index:)on enum values. -
If your schema uses
@condition(override: true)with nested inputs that carry their own@condition, audit whether the legacy "outer owns everything" semantics were load-bearing. If they were, the SQL will change; rewrite the inner annotations to express the intended predicate set explicitly.
Step 1 is non-optional and comes first (the schema does not load at all). Step 2’s two rejections are non-optional too (the build fails); its @table-on-input half warns only, and will become non-optional when that location is rejected. Step 3 is the one to do deliberately rather than reactively, since a schema that skips it keeps building and changes meaning. Steps 4-5 produce deprecation notices but no failures. Step 6 only matters for schemas that relied on the divergence; most schemas don’t.
Pitfalls
-
@notGeneratedis parsed but always rejected. Removing the directive is the only fix; there is no warn-only mode and no migration shim. Schemas that still carry it will not compile. -
The
@field(name:)inference removal leaves no trace to grep for. AnIDslot that used to decode a global ID now binds its column, and both readings resolve, so neither the build log nor the language server flags it. Auditing the SDL is the only way to find these; see step 3 of the sequence above. -
@tablealone no longer auto-promotes toNodeType. If a previously-promoted type breaks under the rewrite (typically:Query.nodereturnsnullfor what used to resolve, or@nodeIdsynthesis stops firing), addimplements Nodeto opt in. Add@nodeon top of that only if the backing jOOQ class publishes no_NODE*metadata. -
Deprecated arguments still work; removed ones do not. A deprecation notice from graphql-java is informational, not blocking, so those migrations are your call until the deprecation flips to removal in a future release.
ExternalCodeReference.name:has already made that flip: it is undeclared, and a schema carrying it does not load. -
The
@conditioncascade divergence is a real SQL change. Schemas that relied on legacy "outer owns everything" semantics produce different result sets under the rewrite, not different errors. Auditoverride: truesites with nested-input@conditionannotations before promoting the rewrite to production. -
The rewrite is directive-compatible, not field-classifier-compatible. A schema that compiled under legacy may surface new diagnostics under the rewrite (stricter classification for shapes the legacy generator silently accepted). The diagnostic always names the field; treat each as the rewrite’s stricter-shape analysis catching what legacy would have produced as a runtime error.
-
Plugin configuration migrates separately. The schema-side migration is what this recipe covers;
<schemaInputs>,<outputDirectory>,<outputPackage>, and<jooqPackage>on the rewrite Mojo do not have a 1:1 mapping with every legacy plugin parameter. The legacy<maxAllowedPageSize>was deliberately excised; How-to: Cursor-paginated connections covers the alternative. The legacyexternalReferences:mappings have no rewrite equivalent at all, since the argument they served is gone.
See also
-
@notGeneratedis the directive surface this recipe expands; it carries the abridged migration matrix in tabular form. -
How-to: Stacking and overriding conditions covers the
@conditioncascade divergence end-to-end. -
How-to: Handle services is the destination for most
@notGeneratedmigrations on root and split-query fields. -
How-to: Result-type variants covers the class-backed-parent migration path (accessor inference vs
@sourceRow), including the reflected backing-class shapes the retired@recorddirective used to declare. -
How-to: Global object IDs covers the
@node/@nodeIddeclaration shapes this recipe migrates to.