There are exactly 5 possibilities (as of graphql-java v12) to provide info to a resolver (DataFetcher) at any level:
1) Directly pass them in the query (possibly on multiple levels):
{customer(id: 3) {
user {
profile(id: 3) {
name
}
}
}
}
2) Get values from the source object
The source is the result of the enclosing query.
In your case, the source for the customer query is the root (whatever you provided at the query execution time, e.g.
graphQL.execute(ExecutionInput.newExecutionInput()
.query(query)
.root(root)
.build())
The source for the user query is whatever customer query returned, presumably some Customer instance.
The source for the profile query is whatever the user query returned, presumably a User instance.
You can get a hold of the source via DataFetchingEnvironment#getSource(). So, if User contains the CustomerID you're after, just get it via ((User) env.getSource()).getCustomerId(). If not, consider wrapping the result into an object that would contain all you need in the sub-queries.
3) Pass the values around using the shared context
graphql-java passes around an instance of GraphQLContext available to all resolvers. So, inside the DataFetcher for customer, you can store the CustomerID into it:
Customer customer = getCustomer();
GraphQLContext context = env.getContext();
context.put("CustomerID", customer.getId());
Later on, inside the DataFetcher for profile, you can get it from the context:
String customerId = env.getContext().get("CustomerID");
To initialize a context, pass it when executing the query:
ExecutionInput input = ExecutionInput.newExecutionInput()
.query(operation)
.graphQLContext(new HashMap<>())
.build()
graphQL.execute(query, input);
This way is stateful, thus the hardest to manage, so use it only if all else fails.
4) Directly get the arguments passed to a parent field
ExecutionStepInfo stepInfo = dataFetchingEnvironment.getExecutionStepInfo();
stepInfo.getParent().getArguments(); // get the parent arguments
5) Pass the values around using the local context
Instead of returning the result directly, wrap it into a DataFetcherResult. That way you can also attach any object as a localContext that will be available to all child DataFetchers via DataFetchingEnvironment#getLocalContext()
Userfrom aCustomerit should carry some (or all) data from theCustomerand likewise toProfile, so (naively) you'd probably do something((Profile)resolveParams.getSource()).getUser().getCustomer().getId()