Marks a GraphQL OBJECT type as the typed home for one or more Java exceptions. When a @service-returned payload exposes an errors: field of an @error type (or a union of @error types), the rewrite generates an error channel: at runtime the service method’s exception is caught, matched against the type’s handlers:, and the matched type is appended to the payload’s errors list.
@error is the entry point to Graphitron’s "errors-as-data" path. It contrasts with throwing: a thrown exception ends the resolver; a mapped exception flows through the GraphQL response as a typed error alongside the rest of the data.
SDL signature
directive @error(handlers: [ErrorHandler!]!) on OBJECT
input ErrorHandler {
handler: ErrorHandlerType! # GENERIC | DATABASE | VALIDATION
className: String # FQCN of the exception (required for GENERIC; defaults to org.jooq.exception.DataAccessException for DATABASE)
code: String # Vendor-specific DB error code (DATABASE only)
sqlState: String # Standardized SQL state (DATABASE only)
matches: String # Substring the exception message must contain
description: String # Override message returned to the client
}
enum ErrorHandlerType { GENERIC, DATABASE, VALIDATION }
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
|
|
(required) |
One or more handler entries describing which Java exceptions map to this error type. Multiple entries on a single type are an "OR" — any matching entry assigns the exception to this type. The first entry whose criteria match (across all |
ErrorHandler fields:
| Field | Required | Description |
|---|---|---|
|
yes |
Selector. |
|
for |
Fully qualified exception class name. Required for |
|
no ( |
Vendor-specific error code as returned by |
|
no ( |
Standardized SQL state from |
|
no |
Substring the exception message must contain. Useful to narrow a broad |
|
no |
Static error message returned to the client. Defaults to the exception’s |
Canonical example
A service payload with a typed errors channel:
type FilmPayload @record(record: {className: "com.example.FilmPayload"}) {
film: Film
errors: [FilmError]
}
union FilmError = YearOutOfRange | NotAllowed | DbError
type YearOutOfRange @error(handlers: [
{handler: DATABASE, sqlState: "23514", matches: "year_check",
description: "Release year must be between 1901 and 2155"}
]) {
path: [String!]!
message: String!
}
type NotAllowed @error(handlers: [
{handler: GENERIC, className: "com.example.NotAllowedException",
description: "You are not allowed to do this"}
]) {
path: [String!]!
message: String!
}
type DbError @error(handlers: [
{handler: DATABASE, sqlState: "23503"}
]) {
path: [String!]!
message: String!
}
type Mutation {
createFilm(input: CreateFilmInput!): FilmPayload @service(service: {className: "com.example.FilmService"})
}
When the service method throws a DataAccessException whose sqlState is 23514 and whose message contains year_check, the rewrite catches it, builds a YearOutOfRange instance with message = description (or the exception’s own message when description: is absent), and appends it to FilmPayload.errors. The data field still streams to the client; only film is null for that request.
VALIDATION handles a Bean-Validation failure raised before the service method runs:
type ValidationErr @error(handlers: [{handler: VALIDATION}]) {
path: [String!]!
message: String!
}
Constraints
-
@erroronly takes effect when the type appears as (or in a union behind) anerrors:field on a payload reachable from a@servicefield. A standalone@errortype with no carrier is rejected at classify time. -
The handler list is order-insensitive within a type; across types of the same channel, ties are broken by handler-tuple equality. Two
@errortypes declaring the same handler tuple (samehandler,className,code,sqlState,matches) collapse — the second is unreachable. The build rejects duplicate tuples. -
matches:makes two otherwise-identical handlers distinct:(GENERIC, IllegalArgumentException, matches: "foo")and(GENERIC, IllegalArgumentException)(nomatches) are both reachable, with the more-specific match running first. -
For
GENERIC,className:is required. The build rejects aGENERIChandler without it. -
For
DATABASE, supply at least one ofcode:,sqlState:, ormatches:(otherwise everyDataAccessExceptionon the channel routes to this type, which is rarely the intent). -
code:andsqlState:may be combined; both must match on the exception. Vendor portability: prefersqlState(PostgreSQL is specific, Oracle is generic) or pair both with amatches:substring. -
Applies only to
OBJECT. Interfaces and unions cannot carry@errordirectly — the union-of-error-types pattern works because the union members are individually@error-decorated.
See also
-
@serviceis the upstream of the errors channel: only service-returned payloads carry one. -
@recordbacks the payload type that exposes theerrors:field. -
How-to: The errors channel covers union vs single-type carriers,
description:vs exception message, and thepath:slot’s contents.