Skip to content

Throw at build for resolvers that can't be called on their JDK dynamic proxy - #913

Merged
oryan-block merged 4 commits into
masterfrom
feature/868-throw-jdk-proxy
Oct 24, 2026
Merged

oryan-block merged 4 commits into
masterfrom
feature/868-throw-jdk-proxy

Conversation

@oryan-block

@oryan-block oryan-block commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #868

Checklist

  • Pull requests follows the contribution guide
  • New or modified functionality is covered by tests

Description

A resolver that is a JDK dynamic proxy (Spring AOP with proxyTargetClass = false) only implements the target's interfaces, so every field that resolves to one of the target's own methods or public fields fails at query time:

object of type jdk.proxy2.$Proxy2 is not an instance of ProxiedQuery

Since #853 the build only warned about it, once per field. Now SchemaClassScanner throws a FieldResolverError instead, also with allowUnimplementedResolvers or a missingResolverDataFetcherProvider, since the resolver exists but can't be called.

The error lists every affected field, not just the first. A JDK-proxied resolver probably breaks all of its fields and a query and a mutation resolver can both be proxied. The scan collects them across all types and throws once before the schema result is built, so the app can fix them in one go instead of one build per field. The per-field sentence is the one the warning had (Field 'Query.test' resolves to ...). The hint to use class-based proxies comes once at the end, with the getter hint added only when one of the fields is a public field:

Field 'Query.test' resolves to ProxiedQuery.test, but the resolver is a jdk.proxy2.$Proxy2, which isn't a ProxiedQuery, so every query of the field will fail.
Field 'Query.count' resolves to ProxiedQuery.count, but the resolver is a jdk.proxy2.$Proxy2, which isn't a ProxiedQuery, so every query of the field will fail.
This happens with JDK dynamic proxies, which only implement interfaces. Use class-based proxies instead, e.g. proxyTargetClass = true in Spring. For a public field, also add a getter, since class-based proxies don't pass field reads to the target.

Another error found earlier in the scan (a missing resolver, for example) still comes first.

parser handles spring AOP proxied resolvers by default only built, so it's now parser rejects spring AOP proxied resolvers that are JDK dynamic proxies. New tests cover the two options above, all fields across a query and a mutation resolver in one error, the getter hint, and a class-based proxy that still builds.

Verified with mvn clean verify on JDK 25 against graphql-java 26.1 and 25.1 (415 tests each). Also through the installed jar with verify-tools, scenario jdk-proxy: on master the build exits 0 with three WARNs (Query.test, Query.count, Query.total), on this branch it exits 2 with one Field ... line per field (same three) in build-error.txt. jdk-proxy-cglib and jdk-proxy-plain still build and run with no warning.

Release note: an app upgrading to 15.0.0 whose resolver is a JDK dynamic proxy (for example Spring AOP without proxyTargetClass = true) fails to build with a FieldResolverError naming every field that can't be called, even if it never queries them. Use class-based proxies (proxyTargetClass = true in Spring), and add a getter for a public field.

@sonarqubecloud

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit f480042 into master Oct 24, 2026
6 checks passed
@oryan-block
oryan-block deleted the feature/868-throw-jdk-proxy branch October 24, 2026 00:25
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.

Throw at build for resolvers that can't be called on their JDK dynamic proxy in 15.0.0

1 participant