Add TypeNameMatcher to RecipeClassLoader allowlist; align with moderne-recipe-loading-commons#7584
Merged
jkschneider merged 2 commits intomainfrom May 7, 2026
Merged
Conversation
`RecipeClassLoader.PARENT_DELEGATED_PREFIXES` includes `org.openrewrite.java.internal.TypesInUse`, ensuring the parent and child classloaders share one `TypesInUse` Class object. The signature of `TypesInUse.hasTypeMatching` was extended with a new parameter type, `org.openrewrite.java.TypeNameMatcher`, but `TypeNameMatcher` was not added to the allowlist. The result: when a recipe-loaded `UsesType.visit()` invokes `TypesInUse.hasTypeMatching(TypeNameMatcher, boolean)`, the recipe classloader resolves `TypeNameMatcher` to its own copy (from the recipe artifact's bundled rewrite-core), while the parent-loaded `TypesInUse` was compiled against the parent's `TypeNameMatcher`. The JVM enforces a loader constraint and throws `LinkageError` because the two classloaders disagree on `TypeNameMatcher`'s identity. Each error fires once per `(recipe instance, source file)` pair and writes a multi-KB stack trace to the `SourcesFileErrors` data table. On Spring-heavy multi-recipe runs this produced hundreds of MB to tens of GB per repo. Adding `TypeNameMatcher` to the allowlist forces both classloaders to resolve to the same `Class` object, satisfying the loader constraint. Validated locally with `mod` CLI: running `io.moderne.java.spring.boot4.SpringBoot4BestPractices` on a Spring-using repo dropped `SourcesFileErrors` from 111,767 rows to 4, eliminating all LinkageError occurrences.
…e-recipe-loading-commons `RecipeClassLoader.PARENT_DELEGATED_PREFIXES` and the parallel `loadFromParent` list in `moderne-recipe-loading-commons` (which this class was forked from in #6437) have drifted. The most acute miss: `org.openrewrite.java.TypeNameMatcher`, recently added as a parameter type to `TypesInUse.hasTypeMatching(TypeNameMatcher, boolean)`. Because `TypesInUse` is in the allowlist (parent-loaded) but `TypeNameMatcher` was not, the recipe artifact's classloader loaded its own copy of `TypeNameMatcher` while `TypesInUse` (parent-loaded) was compiled against the parent's copy. When a recipe-loaded `UsesType.visit()` invokes `TypesInUse.hasTypeMatching(TypeNameMatcher, ...)`, the JVM enforces a loader constraint and throws `LinkageError` because the two classloaders disagree on `TypeNameMatcher`'s identity. The error fires once per (recipe instance, source file) pair, with a multi-KB stack trace recorded in `SourcesFileErrors` each time. Locally validated: with this fix applied, running `io.moderne.java.spring.boot4.SpringBoot4BestPractices` on a small (104 Java file) repo via `mod` CLI dropped `SourcesFileErrors` from 111,767 rows to 4 rows. While here, also bring in the other entries from moderne-recipe-loading-commons that are missing here and reference OSS classes: - org.openrewrite.Singleton - org.openrewrite.gradle.attributes.Category / ProjectAttribute - org.openrewrite.java.Java17Parser - org.openrewrite.maven.attributes.Attributed - org.openrewrite.polyglot - org.openrewrite.protobuf.ProtoVisitor - org.openrewrite.rpc.{Reference, RpcCodec, RpcObjectData, RpcReceiveQueue, RpcRecipe, RpcSendQueue} Entries from the moderne-recipe-loading-commons allowlist that reference proprietary Moderne classes (org.openrewrite.cobol.CobolPreprocessor[Iso]Visitor lives in moderneinc/rewrite-cobol; org.openrewrite.nodejs.NpmExecutor lives in moderneinc/rewrite-nodejs) are intentionally not added here since they don't exist in OSS rewrite-core's classpath. Same for io.moderne.devcenter.* entries.
2af7adb to
40dd096
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
RecipeClassLoader.PARENT_DELEGATED_PREFIXESand the parallelloadFromParentlist inmoderne-recipe-loading-commons(which this class was forked from in #6437) have drifted. The most acute miss:org.openrewrite.java.TypeNameMatcher, recently added as a parameter type toTypesInUse.hasTypeMatching(TypeNameMatcher, boolean).Because
TypesInUseis in the allowlist (parent-loaded) butTypeNameMatcherwas not, the recipe artifact's classloader loaded its own copy ofTypeNameMatcher, whileTypesInUse(parent-loaded) was compiled against the parent's copy. When a recipe-loadedUsesType.visit()invokesTypesInUse.hasTypeMatching(TypeNameMatcher, …), the JVM enforces a loader constraint and throwsLinkageErrorbecause the two classloaders disagree onTypeNameMatcher's identity.In production this fires once per
(recipe instance × source file)pair, with a multi-KB stack trace recorded inSourcesFileErrorseach time. On Spring-heavy customer portfolios it produces hundreds of MB to tens of GB per repo and filled worker disks today (Moderne ops/issues#867).Solution
Add
org.openrewrite.java.TypeNameMatchertoPARENT_DELEGATED_PREFIXESso both the recipe classloader and the parent classloader resolve to the sameTypeNameMatcherClass object.While here, bring in the other entries from
moderne-recipe-loading-commons's allowlist that reference OSS classes and have drifted out of this one over time. They're all engine API surface that recipe code touches and would produce the same kind of failure if the parent and recipe ever held different versions:org.openrewrite.Singleton(added on the commons side in moderneinc/moderne-recipe-loading-commons#69, never ported here)org.openrewrite.gradle.attributes.Category/ProjectAttributeorg.openrewrite.java.Java17Parserorg.openrewrite.maven.attributes.Attributedorg.openrewrite.polyglotorg.openrewrite.protobuf.ProtoVisitororg.openrewrite.rpc.{Reference, RpcCodec, RpcObjectData, RpcReceiveQueue, RpcRecipe, RpcSendQueue}Entries from the commons allowlist that reference proprietary Moderne artifacts are intentionally not included here, since they don't exist in OSS rewrite-core's classpath:
org.openrewrite.cobol.CobolPreprocessor[Iso]Visitorlives inmoderneinc/rewrite-cobol(proprietary)org.openrewrite.nodejs.NpmExecutorlives inmoderneinc/rewrite-nodejs(proprietary)io.moderne.devcenter.*(proprietary)Two further entries that exist in the commons allowlist but appear to refer to nonexistent classes (
org.openrewrite.java.InvocationMatcher;org.openrewrite.maven.table.MavenDownloadEvents) are also not included here, since they would be no-op string matches. They will be cleaned up in a separate PR on the commons side.Validation
Locally: with this fix applied (and the equivalent fix already merged in moderneinc/moderne-recipe-loading-commons#70 for the parallel class used by
moderne-workerv1), runningio.moderne.java.spring.boot4.SpringBoot4BestPracticeson a small (104 Java file) repo viamodCLI dropsSourcesFileErrorsfrom 111,767 rows to 4 rows. The remaining 4 are an unrelatedK.Return.getPrefix()NPE inrewrite-kotlin.Going forward
The two allowlists having drifted is the underlying anti-pattern. Worth tracking a long-term fix (single source of truth, generated allowlist from public method signatures, or CI lint) but out of scope for this PR.