From 067ab5de46e1c64f241243c5ae8cca454b3b0229 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 05:50:36 +0000 Subject: [PATCH 1/2] Initial plan From 25b507152e25c051688ffaa7bf75d0e9632ab156 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 05:54:59 +0000 Subject: [PATCH 2/2] Update to GraphQL Java version 25.0 Co-authored-by: dondonz <13839920+dondonz@users.noreply.github.com> --- documentation/getting-started.mdx | 6 +- docusaurus.config.js | 2 +- .../{version-v24 => version-v25}/batching.md | 64 ++++++++++ .../{version-v24 => version-v25}/concerns.md | 0 .../contributions.md | 0 .../data-fetching.md | 0 .../data-mapping.md | 0 .../directives.md | 0 .../exceptions.md | 0 .../{version-v24 => version-v25}/execution.md | 0 .../field-selection.md | 0 .../field-visibility.md | 0 .../getting-started.mdx | 6 +- .../instrumentation.md | 0 .../{version-v24 => version-v25}/limits.md | 0 versioned_docs/version-v25/profiler.md | 116 ++++++++++++++++++ .../{version-v24 => version-v25}/relay.md | 0 .../{version-v24 => version-v25}/scalars.md | 0 .../{version-v24 => version-v25}/schema.md | 0 .../subscriptions.md | 0 .../upgrade-notes.md | 0 ...idebars.json => version-v25-sidebars.json} | 2 +- versions.json | 2 +- 23 files changed, 189 insertions(+), 9 deletions(-) rename versioned_docs/{version-v24 => version-v25}/batching.md (84%) rename versioned_docs/{version-v24 => version-v25}/concerns.md (100%) rename versioned_docs/{version-v24 => version-v25}/contributions.md (100%) rename versioned_docs/{version-v24 => version-v25}/data-fetching.md (100%) rename versioned_docs/{version-v24 => version-v25}/data-mapping.md (100%) rename versioned_docs/{version-v24 => version-v25}/directives.md (100%) rename versioned_docs/{version-v24 => version-v25}/exceptions.md (100%) rename versioned_docs/{version-v24 => version-v25}/execution.md (100%) rename versioned_docs/{version-v24 => version-v25}/field-selection.md (100%) rename versioned_docs/{version-v24 => version-v25}/field-visibility.md (100%) rename versioned_docs/{version-v24 => version-v25}/getting-started.mdx (95%) rename versioned_docs/{version-v24 => version-v25}/instrumentation.md (100%) rename versioned_docs/{version-v24 => version-v25}/limits.md (100%) create mode 100644 versioned_docs/version-v25/profiler.md rename versioned_docs/{version-v24 => version-v25}/relay.md (100%) rename versioned_docs/{version-v24 => version-v25}/scalars.md (100%) rename versioned_docs/{version-v24 => version-v25}/schema.md (100%) rename versioned_docs/{version-v24 => version-v25}/subscriptions.md (100%) rename versioned_docs/{version-v24 => version-v25}/upgrade-notes.md (100%) rename versioned_sidebars/{version-v24-sidebars.json => version-v25-sidebars.json} (67%) diff --git a/documentation/getting-started.mdx b/documentation/getting-started.mdx index 046d5af2..e2cd4190 100644 --- a/documentation/getting-started.mdx +++ b/documentation/getting-started.mdx @@ -25,7 +25,7 @@ repositories { } dependencies { - implementation 'com.graphql-java:graphql-java:24.3' + implementation 'com.graphql-java:graphql-java:25.0' } ``` @@ -38,7 +38,7 @@ repositories { } dependencies { - implementation("com.graphql-java:graphql-java:24.3") + implementation("com.graphql-java:graphql-java:25.0") } ``` @@ -51,7 +51,7 @@ Dependency: com.graphql-java graphql-java - 24.3 + 25.0 ``` diff --git a/docusaurus.config.js b/docusaurus.config.js index 4fac4f08..ed32b9ae 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -25,7 +25,7 @@ const config = { routeBasePath: 'documentation', sidebarPath: require.resolve('./sidebars.js'), editUrl: 'https://github.com/graphql-java/graphql-java-page/edit/master/', - lastVersion: "v24", + lastVersion: "v25", versions: { current: { label: "master", diff --git a/versioned_docs/version-v24/batching.md b/versioned_docs/version-v25/batching.md similarity index 84% rename from versioned_docs/version-v24/batching.md rename to versioned_docs/version-v25/batching.md index e4201085..6f7a3f3a 100644 --- a/versioned_docs/version-v24/batching.md +++ b/versioned_docs/version-v25/batching.md @@ -370,3 +370,67 @@ DataFetcher dataFetcherThatCallsTheDataLoader = new DataFetcher() { } }; ``` + +## Chained DataLoaders + +The automatic dispatching of Chained DataLoaders is a new feature included in GraphQL Java 25.0 onwards. Before this version, DataLoaders in chains needed to be manually dispatched. + +A Chained DataLoader is where one DataLoader depends on another, within the same DataFetcher. + +For example in code: +```java +DataFetcher> df1 = env -> { + return env.getDataLoader("name").load("Key1").thenCompose(result -> { + return env.getDataLoader("email").load(result); + }); +}; +``` + +### How do I enable Chained DataLoaders? +You must opt-in to Chained DataLoaders via `GraphQLUnusualConfiguration.DataloaderConfig`, as this may change order of dispatching. + +Set `enableDataLoaderChaining(true)` to enable Chained DataLoaders. + +For example, to set `enableDataLoaderChaining`: +```java +GraphQL graphQL = GraphQL.unusualConfiguration(graphqlContext) + .dataloaderConfig() + .enableDataLoaderChaining(true); +``` + +### What changed in GraphQL Java 25.0? +The DataFetcher in the example above, before version 25.0 would have caused execution to hang, because the second DataLoader ("email") was never dispatched. + +Prior to version 25.0, users of GraphQL Java needed to manually dispatch DataLoaders to ensure execution completed. From version 25.0, the GraphQL Java engine will automatically dispatch Chained DataLoaders. + +If you're looking for more examples, and the technical details, please see [our tests](https://github.com/graphql-java/graphql-java/blob/master/src/test/groovy/graphql/ChainedDataLoaderTest.groovy). + +Note: The GraphQL Java engine can only optimally calculate DataLoader dispatches on a per-level basis. It does not calculate optimal DataLoader dispatching across different levels of an operation's field tree. + +### A special case: Delayed DataLoaders + +In a previous code snippet, we demonstrated one DataLoader depending on another DataLoader. + +Another special case is a "delayed" DataLoader, where a DataLoader depends on a slow async task instead. For example, here are two DataFetchers from [a test example](https://github.com/graphql-java/graphql-java/blob/master/src/test/groovy/graphql/ChainedDataLoaderTest.groovy): + +```groovy +def fooDF = { env -> + return supplyAsync { + Thread.sleep(1000) + return "fooFirstValue" + }.thenCompose { + return env.getDataLoader("dl").load(it) + } +} as DataFetcher + +def barDF = { env -> + return supplyAsync { + Thread.sleep(1000) + return "barFirstValue" + }.thenCompose { + return env.getDataLoader("dl").load(it) + } +} as DataFetcher +``` + +By opting in to Chained DataLoaders, GraphQL Java will also calculate when to dispatch "delayed" DataLoaders. These "delayed" DataLoaders will be enqueued for dispatch after the async task completes. diff --git a/versioned_docs/version-v24/concerns.md b/versioned_docs/version-v25/concerns.md similarity index 100% rename from versioned_docs/version-v24/concerns.md rename to versioned_docs/version-v25/concerns.md diff --git a/versioned_docs/version-v24/contributions.md b/versioned_docs/version-v25/contributions.md similarity index 100% rename from versioned_docs/version-v24/contributions.md rename to versioned_docs/version-v25/contributions.md diff --git a/versioned_docs/version-v24/data-fetching.md b/versioned_docs/version-v25/data-fetching.md similarity index 100% rename from versioned_docs/version-v24/data-fetching.md rename to versioned_docs/version-v25/data-fetching.md diff --git a/versioned_docs/version-v24/data-mapping.md b/versioned_docs/version-v25/data-mapping.md similarity index 100% rename from versioned_docs/version-v24/data-mapping.md rename to versioned_docs/version-v25/data-mapping.md diff --git a/versioned_docs/version-v24/directives.md b/versioned_docs/version-v25/directives.md similarity index 100% rename from versioned_docs/version-v24/directives.md rename to versioned_docs/version-v25/directives.md diff --git a/versioned_docs/version-v24/exceptions.md b/versioned_docs/version-v25/exceptions.md similarity index 100% rename from versioned_docs/version-v24/exceptions.md rename to versioned_docs/version-v25/exceptions.md diff --git a/versioned_docs/version-v24/execution.md b/versioned_docs/version-v25/execution.md similarity index 100% rename from versioned_docs/version-v24/execution.md rename to versioned_docs/version-v25/execution.md diff --git a/versioned_docs/version-v24/field-selection.md b/versioned_docs/version-v25/field-selection.md similarity index 100% rename from versioned_docs/version-v24/field-selection.md rename to versioned_docs/version-v25/field-selection.md diff --git a/versioned_docs/version-v24/field-visibility.md b/versioned_docs/version-v25/field-visibility.md similarity index 100% rename from versioned_docs/version-v24/field-visibility.md rename to versioned_docs/version-v25/field-visibility.md diff --git a/versioned_docs/version-v24/getting-started.mdx b/versioned_docs/version-v25/getting-started.mdx similarity index 95% rename from versioned_docs/version-v24/getting-started.mdx rename to versioned_docs/version-v25/getting-started.mdx index 046d5af2..e2cd4190 100644 --- a/versioned_docs/version-v24/getting-started.mdx +++ b/versioned_docs/version-v25/getting-started.mdx @@ -25,7 +25,7 @@ repositories { } dependencies { - implementation 'com.graphql-java:graphql-java:24.3' + implementation 'com.graphql-java:graphql-java:25.0' } ``` @@ -38,7 +38,7 @@ repositories { } dependencies { - implementation("com.graphql-java:graphql-java:24.3") + implementation("com.graphql-java:graphql-java:25.0") } ``` @@ -51,7 +51,7 @@ Dependency: com.graphql-java graphql-java - 24.3 + 25.0 ``` diff --git a/versioned_docs/version-v24/instrumentation.md b/versioned_docs/version-v25/instrumentation.md similarity index 100% rename from versioned_docs/version-v24/instrumentation.md rename to versioned_docs/version-v25/instrumentation.md diff --git a/versioned_docs/version-v24/limits.md b/versioned_docs/version-v25/limits.md similarity index 100% rename from versioned_docs/version-v24/limits.md rename to versioned_docs/version-v25/limits.md diff --git a/versioned_docs/version-v25/profiler.md b/versioned_docs/version-v25/profiler.md new file mode 100644 index 00000000..9f107962 --- /dev/null +++ b/versioned_docs/version-v25/profiler.md @@ -0,0 +1,116 @@ +--- +title: "Profiling requests" +date: 2025-08-05T12:52:46+10:00 +--- + +# Profiling GraphQL requests + +We've introduced a new profiling feature to help you understand the performance of your GraphQL executions. It provides detailed insights into DataFetcher calls, Dataloader usage, and execution timing. This guide will show you how to use it and interpret the results. + +The Profiler is available in all versions after v25.0.beta-5. It will also be included in the forthcoming official v25.0 release. + +## Enabling the Profiler + +To enable profiling for a GraphQL execution, you need to set a flag on your `ExecutionInput`. It's as simple as calling `.profileExecution(true)` when building it. + +```java +import graphql.ExecutionInput; +import graphql.GraphQL; + +// ... +GraphQL graphql = GraphQL.newGraphQL(schema).build(); + +ExecutionInput executionInput = ExecutionInput.newExecutionInput() + .query("query Hello { world }") + .profileExecution(true) // Enable profiling + .build(); + +graphql.execute(executionInput); +``` + +## Accessing the Profiler Results + +The profiling results are stored in the `GraphQLContext` associated with your `ExecutionInput`. After the execution is complete, you can retrieve the `ProfilerResult` object from the context. + +The result object is stored under the key `ProfilerResult.PROFILER_CONTEXT_KEY`. + +```java +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.ProfilerResult; + +// ... +ExecutionInput executionInput = /* ... see above ... */ +ExecutionResult executionResult = graphql.execute(executionInput); + +ProfilerResult profilerResult = executionInput.getGraphQLContext().get(ProfilerResult.PROFILER_CONTEXT_KEY); + +if (profilerResult != null) { + Map summary = profilerResult.shortSummaryMap(); + System.out.println(summary); // or log as you prefer +} +``` + +## Understanding the Profiler Results + +A great way to get a quick overview about `ProfilerResult` is by using the `shortSummaryMap()` method. It returns a map with key metrics about the execution, which you can use for logging. Let's break down what each key means. + +### The ProfilerResult Short Summary Map + +Here's a detailed explanation of the fields you'll find in the map returned by `shortSummaryMap()`: + +| Key | Type | Description | +| ------------------------------------- | ----------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `executionId` | `String` | The unique ID for this GraphQL execution. | +| `operationName` | `String` | The name of the operation, if one was provided in the query (e.g., `query MyQuery { ... }`). | +| `operationType` | `String` | The type of operation, such as `QUERY`, `MUTATION`, or `SUBSCRIPTION`. | +| `startTimeNs` | `long` | The system time in nanoseconds when the execution started. | +| `endTimeNs` | `long` | The system time in nanoseconds when the execution finished. | +| `totalRunTimeNs` | `long` | The total wall-clock time of the execution (`endTimeNs - startTimeNs`). This includes time spent waiting for asynchronous operations like database calls or external API requests within your DataFetchers. | +| `engineTotalRunningTimeNs` | `long` | The total time the GraphQL engine spent actively running on a thread. This is like the "CPU time" of the execution and excludes time spent waiting for `CompletableFuture`s to complete. Comparing this with `totalRunTimeNs` can give you a good idea of how much time is spent on I/O. | +| `totalDataFetcherInvocations` | `int` | The total number of times any DataFetcher was invoked. | +| `totalCustomDataFetcherInvocations` | `int` | The number of invocations for DataFetchers you've written yourself (i.e., not the built-in `PropertyDataFetcher`). | +| `totalTrivialDataFetcherInvocations` | `int` | The number of invocations for the built-in `PropertyDataFetcher`. | +| `totalWrappedTrivialDataFetcherInvocations` | `int` | The number of invocations for DataFetchers that wrap a `PropertyDataFetcher`. | +| `fieldsFetchedCount` | `int` | The number of unique fields fetched during the execution. | +| `dataLoaderChainingEnabled` | `boolean` | `true` if the experimental Dataloader chaining feature was enabled for this execution. | +| `dataLoaderLoadInvocations` | `Map` | A map where keys are Dataloader names and values are the number of times `load()` was called on them. Note that this counts all `load()` calls, including those that hit the Dataloader cache. | +| `oldStrategyDispatchingAll` | `Set` | If Chained DataLoaders are not used, the older dispatching strategy is used instead. This key lists the levels where DataLoaders were dispatched. | +| `dispatchEvents` | `List` | A list of events, one for each time a Dataloader was dispatched. See below for details. | +| `instrumentationClasses` | `List` | A list of the class names of the `Instrumentation`s used during this execution. | +| `dataFetcherResultTypes` | `Map` | A summary of the types of values returned by your custom DataFetchers. See below for details. | + +#### `dispatchEvents` + +This is a list of maps, each detailing a `DataLoader` dispatch event. + +| Key | Type | Description | +| -------------- | -------- | -------------------------------------------------------------------- | +| `type` | `String` | The type of dispatch. Can be `LEVEL_STRATEGY_DISPATCH`, `CHAINED_STRATEGY_DISPATCH`, `DELAYED_DISPATCH`, `CHAINED_DELAYED_DISPATCH`, or `MANUAL_DISPATCH`. | +| `dataLoader` | `String` | The name of the `DataLoader` that was dispatched. | +| `level` | `int` | The execution strategy level at which the dispatch occurred. | +| `keyCount` | `int` | The number of keys that were dispatched in this batch. | + +#### `dataFetcherResultTypes` + +This map gives you more information into the type of your DataFetchers' return values. + +The keys are `COMPLETABLE_FUTURE_COMPLETED`, `COMPLETABLE_FUTURE_NOT_COMPLETED`, and `MATERIALIZED`. +Each key maps to another map with two keys: +* `count`: The number of unique fields with DataFetchers that returned this result type. +* `invocations`: The total number of invocations across all fields that returned this result type. + +Here's what the result types mean: + +| Result Type | Meaning | +| ---------------------------------- | --------------------------------------------------------------------------------------------- | +| `COMPLETABLE_FUTURE_COMPLETED` | The DataFetcher returned a `CompletableFuture` that was already completed when it was returned. | +| `COMPLETABLE_FUTURE_NOT_COMPLETED` | The DataFetcher returned an incomplete `CompletableFuture`. | +| `MATERIALIZED` | The DataFetcher returned a simple value (i.e., not a `CompletableFuture`). | + +### A note on engine timing statistics logged from an `Instrumentation` + +If you're logging the `ProfilerResult` from an `Instrumentation`, note that engine timing statistics such as `startTimeNs`, `endTimeNs`, `totalRunTimeNs`, `engineTotalRunningTimeNs` will be set to `0`. This is because the timing is set after all `Instrumentation`s have run, so it is not available from within an `Instrumentation`. + +Apart from engine timing information, all other `ProfilerResult` information is still valid if accessed from within an `Instrumentation`. diff --git a/versioned_docs/version-v24/relay.md b/versioned_docs/version-v25/relay.md similarity index 100% rename from versioned_docs/version-v24/relay.md rename to versioned_docs/version-v25/relay.md diff --git a/versioned_docs/version-v24/scalars.md b/versioned_docs/version-v25/scalars.md similarity index 100% rename from versioned_docs/version-v24/scalars.md rename to versioned_docs/version-v25/scalars.md diff --git a/versioned_docs/version-v24/schema.md b/versioned_docs/version-v25/schema.md similarity index 100% rename from versioned_docs/version-v24/schema.md rename to versioned_docs/version-v25/schema.md diff --git a/versioned_docs/version-v24/subscriptions.md b/versioned_docs/version-v25/subscriptions.md similarity index 100% rename from versioned_docs/version-v24/subscriptions.md rename to versioned_docs/version-v25/subscriptions.md diff --git a/versioned_docs/version-v24/upgrade-notes.md b/versioned_docs/version-v25/upgrade-notes.md similarity index 100% rename from versioned_docs/version-v24/upgrade-notes.md rename to versioned_docs/version-v25/upgrade-notes.md diff --git a/versioned_sidebars/version-v24-sidebars.json b/versioned_sidebars/version-v25-sidebars.json similarity index 67% rename from versioned_sidebars/version-v24-sidebars.json rename to versioned_sidebars/version-v25-sidebars.json index 4d18c862..00029a1e 100644 --- a/versioned_sidebars/version-v24-sidebars.json +++ b/versioned_sidebars/version-v25-sidebars.json @@ -1,5 +1,5 @@ { - "version-v24/tutorialSidebar": [ + "version-v25/tutorialSidebar": [ { "type": "autogenerated", "dirName": "." diff --git a/versions.json b/versions.json index 46a4e005..4a8ffe93 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ [ - "v24" + "v25" ]