Skip to content

feat: add the GraphQL extension spike (G0) - #1589

Merged
jlouvel merged 2 commits into
3.0from
feat/graphql-g0-spike
Nov 1, 2026
Merged

jlouvel merged 2 commits into
3.0from
feat/graphql-g0-spike

Conversation

@jlouvel

@jlouvel jlouvel commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Closes #1582. Part of #1581.

Summary

Adds org.restlet.ext.graphql, a new experimental extension that hosts graphql-java 26.1 behind a Restlet and sends GraphQL operations through a borrowed org.restlet.Client. graphql-java types (GraphQL, ExecutionResult, GraphQLError, Document) are the public model; the extension only implements the HTTP binding.

GraphqlServerAdapter graphql = new GraphqlServerAdapter(getContext());
graphql.setGraphQL(GraphQL.newGraphQL(schema).build());
router.attach("/graphql", graphql);

GraphqlClientAdapter client = new GraphqlClientAdapter(getContext());
client.setClient(restletClient);          // borrowed, never stopped
client.setEndpoint("https://example.com/graphql");
client.start();
ExecutionResult result = client.execute("query($id: ID!) { issue(id: $id) { title } }", null, Map.of("id", "1"));
  • Server: POST with a JSON body; execution on the call's virtual thread (asynchronous fetchers are joined there); application/graphql-response+json when the client names it, application/json otherwise (wildcards and no Accept select application/json, per the draft's legacy rule); 400 with a GraphQL error body for malformed requests, 405 for other methods, 406 when neither type is acceptable.
  • Client: constructor and setters frozen by start(); native ExecutionResult values; GraphqlTransportException with the status and a bounded body excerpt for connection failures and non-GraphQL responses (HTML 502, empty 401, invalid JSON). Response size is bounded (10 MiB).
  • Dependencies: org.restlet, graphql-java and jackson-databind 3 directly (not org.restlet.ext.jackson, to avoid its CSV, Smile, XML and YAML formats).

Spike findings

  • JPMS: graphql-java 26.1, java-dataloader 6.0.0 and reactive-streams 1.0.3 have no module descriptor. Their automatic module names are com.graphqljava, org.dataloader and org.reactivestreams. GraphqlModulePathTestCase compiles a named consumer module with javac and runs a full round trip in a ModuleLayer built from the real artifacts.
  • Threads: data fetchers run on the server call's virtual thread; a CompletableFuture fetcher completes and is joined there.
  • Error messages: graphql-java's default SimpleDataFetcherExceptionHandler puts the exception message in the error (Exception while fetching data (/failing) : …). The extension doesn't change errors; applications that must hide messages configure their own DataFetcherExceptionHandler. The test pins this behavior.
  • Parser limits: graphql-java reads ParserOptions from the GraphQLContext of each ExecutionInput, so limits can be applied per execution without touching the process-wide defaults (used in G1).

Validation

./mvnw -B verify -P default,test -pl org.restlet.ext.graphql: 13 tests, 0 failures; 89% instruction coverage (gate 80%); Spotless, SpotBugs (0 findings), Javadoc, -Werror and dependency convergence pass.

  • GraphqlSharedPortTestCase: a REST resource, a query and a mutation on one HTTP/2 (h2c) server connector; virtual threads; partial data with field errors; transport failure; lifecycle and borrowed-client ownership.
  • GraphqlHttpErrorsTestCase: 405, 400 (no body, invalid JSON, batch array, missing or non-string query), negotiation and 406, operationName/variables/extensions forwarding, start without a GraphQL, client transport errors and request errors as results.
  • GraphqlModulePathTestCase: named-module consumer.

Next

G1 (#1583) completes the server binding (GET, status codes per media type, limits, request context) on top of this branch.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved content-negotiation and response-handling defects can select explicitly rejected JSON, drop explicit null data, and throw uncaught runtime exceptions instead of diagnostic transport exceptions.

4 open issues
What changed in this PR

Adds the experimental GraphQL G0 extension, enabling GraphQL operations alongside REST resources through Restlet’s existing connectors. This establishes the module and adapter foundation for the later HTTP-binding phases.

Changes:

  • Adds server and borrowed-client adapters using native graphql-java types, JSON transport, and transport exceptions.
  • Registers the Maven and Java modules with graphql-java 26.1 and Jackson 3.
  • Adds HTTP/2 round-trip, lifecycle, error-handling, and named-module tests, plus release notes.
File Description
pom.xml Registers the extension and graphql-java version.
org.restlet.ext.graphql/​src/​test/​java/​org/​restlet/​ext/​graphql/​GraphqlTestSchemas.java Supplies shared query and mutation schemas.
org.restlet.ext.graphql/​src/​test/​java/​org/​restlet/​ext/​graphql/​GraphqlSharedPortTestCase.java Tests REST/GraphQL coexistence over HTTP/2 and lifecycle behavior.
org.restlet.ext.graphql/​src/​test/​java/​org/​restlet/​ext/​graphql/​GraphqlModulePathTestCase.java Compiles and runs a named-module consumer.
org.restlet.ext.graphql/​src/​test/​java/​org/​restlet/​ext/​graphql/​GraphqlHttpErrorsTestCase.java Tests request errors, negotiation, and transport failures.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​package-info.java Documents the experimental public adapters.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​internal/​package-info.java Documents the internal package.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​internal/​GraphqlMediaTypes.java Defines response media types and negotiation.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​internal/​GraphqlJson.java Provides JSON conversion and bounded reads.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​GraphqlTransportException.java Exposes transport failure status and body excerpts.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​GraphqlServerAdapter.java Hosts GraphQL execution through a POST endpoint.
org.restlet.ext.graphql/​src/​main/​java/​org/​restlet/​ext/​graphql/​GraphqlClientAdapter.java Sends operations through a borrowed Restlet client.
org.restlet.ext.graphql/​src/​main/​java/​module-info.java Declares module dependencies and public exports.
org.restlet.ext.graphql/​pom.xml Defines extension dependencies.
changes.md Records the experimental extension and dependency addition.

Review effort: Balanced

@jlouvel
jlouvel force-pushed the feat/graphql-g0-spike branch from c64f032 to 0fb3e9e Compare November 1, 2026 14:23
jlouvel and others added 2 commits November 1, 2026 15:09
Add org.restlet.ext.graphql, hosting graphql-java 26.1 behind a Restlet
and sending GraphQL operations through a borrowed Client:

- GraphqlServerAdapter: POST with a JSON body, execution on the call's
  virtual thread, application/graphql-response+json or application/json
  responses, 400 for malformed requests, 405 for other methods.
- GraphqlClientAdapter: constructor and setters frozen by start(),
  native ExecutionResult values, GraphqlTransportException with the
  status and a body excerpt when no GraphQL response is received.
- graphql-java, java-dataloader and reactive-streams have no module
  descriptor. A test compiles a named consumer module and runs a round
  trip in a module layer using their automatic module names
  (com.graphqljava, org.dataloader, org.reactivestreams).
- A query and a mutation round trip on one HTTP/2 port next to a REST
  resource.

Refs #1582

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jerome Louvel <374450+jlouvel@users.noreply.github.com>
Treat an entity without a stream as an empty response, report malformed
results as transport exceptions, keep an explicit null data entry, and
use the quality of the most specific matching Accept range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jlouvel
jlouvel force-pushed the feat/graphql-g0-spike branch from 0fb3e9e to 4990292 Compare November 1, 2026 20:09
@jlouvel
jlouvel merged commit 96b5c92 into 3.0 Nov 1, 2026
1 check passed
@jlouvel
jlouvel deleted the feat/graphql-g0-spike branch November 1, 2026 20:10
@sonarqubecloud

sonarqubecloud Bot commented Nov 1, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants