Marks a field as resolved by a static Java method instead of by direct column projection or by a @reference join. The method takes the parent table’s jOOQ class as its sole argument and returns a Field<T> whose T matches the GraphQL scalar; the rewrite drops the returned Field into the parent’s SELECT clause at the field’s slot. The method name defaults to the GraphQL field name when method: is omitted from the reference, removing ceremony from the common case.
@externalField is the seam for custom column-shaped logic: computed booleans, derived strings, vendor-specific SQL expressions, and jOOQ Row.convertFrom(…) lifts that fabricate typed records (e.g. lifting an FK column into a Field<TableRecord<?>> that subsequent child fields read through). It contrasts with @reference (which traverses an FK chain) and with @service (which calls into a service for the entire payload): @externalField produces a single Field<T> and that’s all.
SDL signature
directive @externalField(reference: ExternalCodeReference!) on FIELD_DEFINITION
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
|
|
(required) |
Points at the static Java method that produces the field’s |
Canonical examples
A computed boolean derived from a column on the parent table. The method name is omitted because it equals the GraphQL field name:
type Film @table(name: "film") {
isEnglish: Boolean @externalField(reference: {
className: "com.example.FilmExtensions"
})
}
public final class FilmExtensions {
public static Field<Boolean> isEnglish(Film film) {
return DSL.iif(film.ORIGINAL_LANGUAGE_ID.eq(1), inline(true), inline(false));
}
}
A Field<TableRecord<?>> lift via DSL.row(…).convertFrom(…). The lifted FilmRecord carries only the PK column, child fields on the resulting @record type read from the lifted record and the framework batch-fetches non-PK columns as needed:
type Inventory @table(name: "inventory") {
inventoryId: Int! @field(name: "INVENTORY_ID")
filmRef: FilmCard @externalField(reference: {
className: "com.example.InventoryExtensions",
method: "filmRef"
})
}
type FilmCard @record(record: {className: "com.example.jooq.tables.records.FilmRecord"}) {
filmId: Int @field(name: "film_id")
}
A Field<CustomJavaRecord> lift returning a developer-defined record whose canonical accessor returns a typed jOOQ record. The classifier auto-derives an AccessorKeyedSingle batch key from the typed accessor, so a child @table-returning field on the wrapper resolves through a DataLoader keyed on the lifted record’s PK:
type Inventory @table(name: "inventory") {
filmCardData: FilmCardWrapper @externalField(reference: {
className: "com.example.InventoryExtensions",
method: "filmCardData"
})
}
type FilmCardWrapper @record(record: {className: "com.example.services.FilmCardData"}) {
film: Film
}
Constraints
-
The parent type must be
@table-bound. The static method’s sole formal parameter is the parent table’s jOOQ class; without that anchor, reflection has no shape to bind. -
The GraphQL field name must not collide with a real SQL column on the parent table. The wiring side resolves the field by name via
DSL.field("<name>")against the resultRecord, a collision shadows the actual column. Rename the field or use@field(name: …)to disambiguate. -
className:is required on the reference. A bare@externalField(noreference:, or a reference with noclassName:) surfaces asexternal field reference could not be resolved — missing className. -
method:is optional. When omitted, the rewrite uses the GraphQL field name as the static-method name. Setmethod:only when the two diverge. -
argMapping:on thereferenceis structurally inert and rejected at parse time.@externalFieldconsumes no GraphQL-argument-bound parameters. -
The static method must return
Field<T>whereTmatches the GraphQL scalar, or one of the lift forms (Field<TableRecord<?>>,Field<CustomJavaRecord>) when the field’s GraphQL type is an@record. -
className:must be a fully-qualified class name reachable on the rewrite Maven plugin’s classpath (declare the carrying artifact under<dependencies>of the plugin’s<plugin>block). Reflection failures surface asexternal field reference could not be resolved — <reflection error>. -
Combining
@externalFieldwith a@referencepath (the condition-join lift form) is not yet supported; the build rejects this with a deferred-validation error.
See also
-
@referenceis the FK-driven counterpart, choose it when the value comes from a chain of catalog joins, choose@externalFieldwhen the value is a custom expression or a typed lift. -
@recordis the natural target type forField<TableRecord<?>>andField<CustomJavaRecord>lift forms. -
@field(name: …)disambiguates the GraphQL-field-vs-SQL-column alias when names collide. -
How-to: Computed fields with
@externalFieldis the operational recipe; it walks the(ParentTable) → Field<T>signature, the three return shapes (scalar,Field<TableRecord<?>>,Field<CustomJavaRecord>), and the canonical fixtures. -
How-to: Wire external Java code covers the rewrite plugin’s classpath setup, the FQCN requirement, and the method-name default shared with
@service,@condition, and the other external-code directives.