Skip to content

Fix StackOverflowError in JavaTypeVisitor for circular type references#6492

Merged
sambsnyd merged 7 commits intoopenrewrite:mainfrom
mcebanupgrade:fix/stackoverflow-circular-types
Jan 15, 2026
Merged

Fix StackOverflowError in JavaTypeVisitor for circular type references#6492
sambsnyd merged 7 commits intoopenrewrite:mainfrom
mcebanupgrade:fix/stackoverflow-circular-types

Conversation

@mcebanupgrade
Copy link
Copy Markdown
Contributor

@mcebanupgrade mcebanupgrade commented Jan 7, 2026

Summary

Fixes StackOverflowError when JavaTypeVisitor encounters circular type references in parameterized types, such as builder patterns like Builder<T extends Builder<T>>.

Problem

When visiting types with circular references, JavaTypeVisitor would infinitely recurse through:

  • visitClassgetMethods()visitMethodgetDeclaringType() → back to visitClass

This commonly occurs with:

  • Builder patterns: interface Builder<T extends Builder<T>>
  • OpenSearch/Elasticsearch Java client code
  • Any fluent APIs with recursive generic bounds

Solution

Added visited type tracking using an IdentityHashMap-backed Set in JavaTypeVisitor.visit(). When a type instance is encountered that has already been visited, it is returned unchanged.

Key changes:

  • Added visited field to track all visited type instances
  • Check if type was visited before processing
  • Also track transformed types from preVisit()
  • Uses identity comparison (same object instance) to detect cycles

This prevents both:

  1. Infinite recursion (cycles in the type graph)
  2. Exponential explosion (revisiting same types from different paths)

Test Plan

  • Added JavaTypeVisitorStackOverflowTest with real-world builder pattern code
  • Test fails with StackOverflowError without fix
  • Test passes in ~17 seconds with fix
  • All existing *JavaTypeVisitor* tests pass
  • All *ChangeType* tests pass
  • All *UnsafeReplaceJavaType* tests pass
  • Verified fix with real-world project that was failing

🤖 Generated with Claude Code

This test demonstrates a StackOverflowError that occurs when JavaTypeVisitor
encounters circular type references in parameterized types, particularly
common in builder patterns like Builder<T extends Builder<T>>.

The test creates a circular type structure programmatically and attempts to
visit it with JavaTypeVisitor. Currently, this causes infinite recursion:

  JavaTypeVisitor.visitParameterized()
    -> visit()
    -> ListUtils.map()
    -> visit()
    -> visitParameterized()
    -> (infinite loop until StackOverflowError)

This issue affects real-world code, including:
- Builder patterns with recursive generic bounds
- OpenSearch Java client code
- Any fluent APIs using self-referential generics

The test currently fails with StackOverflowError. Once the fix is applied
(cycle detection in JavaTypeVisitor.visit()), this test should pass.

Test includes:
1. testCircularTypeReferencesCauseStackOverflow - Direct reproduction
2. testRealWorldBuilderPatternCausesStackOverflow - Integration test

Issue: Circular type references cause StackOverflowError
@mcebanupgrade mcebanupgrade force-pushed the fix/stackoverflow-circular-types branch from dd71946 to 1c9c32b Compare January 7, 2026 19:43
Add visited type tracking to JavaTypeVisitor.visit() to prevent infinite
recursion when encountering circular type references in parameterized types.

The fix uses an IdentityHashMap-backed Set to track all types that have
been visited. Unlike the previous approach which only tracked types on
the current call stack, this approach:

1. Prevents infinite recursion (cycles)
2. Prevents exponential explosion from revisiting the same types via
   different paths in the type graph

When a type is encountered that has already been visited, it is returned
unchanged, breaking both cycles and redundant visits.

Key changes:
- Added 'visited' field to track all visited types
- Check if type was visited before processing
- Also track transformed types from preVisit()
- Uses identity comparison (same object instance) not equals()

This handles common patterns that cause StackOverflowError:
- Builder patterns: Builder<T extends Builder<T>>
- OpenSearch/Elasticsearch Java client code
- Any fluent APIs with recursive generic bounds

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mcebanupgrade mcebanupgrade changed the title Add test reproducing StackOverflowError in JavaTypeVisitor Fix StackOverflowError in JavaTypeVisitor for circular type references Jan 7, 2026
@mcebanupgrade mcebanupgrade marked this pull request as ready for review January 7, 2026 21:41
@timtebeek timtebeek added the bug Something isn't working label Jan 7, 2026
@timtebeek
Copy link
Copy Markdown
Member

Thanks for logging the issue and immediately proposing a fix! I can see JavaTypeVisitor is used in:

  1. Substitutions

    private void extractTypeVariables(@Nullable JavaType type) {
    if (type == null) {
    return;
    }
    Set<JavaType> visited = newSetFromMap(new IdentityHashMap<>());
    new JavaTypeVisitor<Integer>() {
    @Override
    public JavaType visitAnnotation(JavaType.Annotation annotation, Integer p) {
    return annotation.getType();
    }
    @Override
    public JavaType visitArray(JavaType.Array array, Integer p) {
    visit(array.getElemType(), p);
    return array;
    }
    @Override
    public JavaType visitClass(JavaType.Class aClass, Integer p) {
    return aClass;
    }
    @Override
    public JavaType visitGenericTypeVariable(JavaType.GenericTypeVariable generic, Integer p) {
    if (!visited.add(generic)) {
    return generic;
    }
    if (!"?".equals(generic.getName())) {
    typeVariables.add(generic);
    }
    return super.visitGenericTypeVariable(generic, p);
    }
    @Override
    public JavaType visitMethod(JavaType.Method method, Integer p) {
    return method;
    }
    @Override
    public JavaType visitParameterized(JavaType.Parameterized parameterized, Integer p) {
    for (JavaType typeParameter : parameterized.getTypeParameters()) {
    visit(typeParameter, p);
    }
    return super.visitParameterized(parameterized, p);
    }
    @Override
    public JavaType visitVariable(JavaType.Variable variable, Integer p) {
    return variable;
    }
    }.visit(type, 0);
    }

  2. J.CompilationUnit.getWeight (as well as JS.CompilationUnit, K.CompilationUnit,

    public long getWeight(Predicate<Object> uniqueIdentity) {
    AtomicInteger n = new AtomicInteger();
    new JavaVisitor<AtomicInteger>() {
    final JavaTypeVisitor<AtomicInteger> typeVisitor = new JavaTypeVisitor<AtomicInteger>() {
    @Override
    public JavaType visit(@Nullable JavaType javaType, AtomicInteger n) {
    if (javaType != null && uniqueIdentity.test(javaType)) {
    n.incrementAndGet();
    return super.visit(javaType, n);
    }
    //noinspection ConstantConditions
    return javaType;
    }
    };

  3. UnsafeJavaTypeVisitor, which in turn is used by Moderne internals

  4. JavaTypeReceiver and JavaTypeSender

As such we're a little sensitive to performance changes with the new set introduced and evaluated for every type visited:

/**
* Track types that have been visited to prevent infinite recursion and exponential
* explosion from revisiting the same types via different paths.
* Uses identity comparison since we care about the same object instance.
*/
@Nullable
private Set<JavaType> visited;

@knutwannheden do you see any concerns or alternatives to be able to support visiting circular type references?

Copy link
Copy Markdown
Member

@sambsnyd sambsnyd left a comment

Choose a reason for hiding this comment

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

We didn't build this visited set into the base class at the beginning on purpose. In case there were scenarios where it wouldn't be desirable or a different criteria might be used to determine when to keep visiting and when to stop.

I surveyed all the uses of JavaTypeVisitor in the OpenRewrite and Moderne codebases and most use this pattern. But some have variations on it such that I'd be a bit worried about regression.

So I recommend making this a subclass of JavaTypeVisitor to preserve the optionality of the cycle-avoidance criteria. Perhaps named StandardJavaTypeVisitor or UniqueJavaTypeVisitor

timtebeek added a commit that referenced this pull request Jan 7, 2026
As discovered when exploring #6492 usages of JavaTypeVisitor
@timtebeek timtebeek marked this pull request as draft January 7, 2026 22:46
@timtebeek
Copy link
Copy Markdown
Member

Marked as draft based on the above; perhaps it's also good to know what code path led you into JavaTypeVisitor, as there's a couple different options but none that stand out as taken by default. Is there a recipe you're executing that acts on these types? That way we can better judge if and where a subclass visitor would need to be applied.

timtebeek added a commit that referenced this pull request Jan 7, 2026
As discovered when exploring #6492 usages of JavaTypeVisitor
@mcebanupgrade
Copy link
Copy Markdown
Contributor Author

Here's what I managed to extract from the stacktrace.
I guess the "path" you're referring to is S3StreamingResponseToV2 ?
I tried using their latest version, getting the same loop

... infinit loop at this point
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.JavaTypeVisitor.lambda$visit$0 (JavaTypeVisitor.java:36)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.JavaTypeVisitor.lambda$visit$0 (JavaTypeVisitor.java:36)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.JavaTypeVisitor.lambda$visit$0 (JavaTypeVisitor.java:36)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.JavaTypeVisitor.lambda$visit$0 (JavaTypeVisitor.java:36)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.JavaTypeVisitor.lambda$visit$0 (JavaTypeVisitor.java:36)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.JavaTypeVisitor.lambda$visit$0 (JavaTypeVisitor.java:36)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:36)
    at org.openrewrite.java.JavaTypeVisitor.visitParameterized (JavaTypeVisitor.java:181)
    at org.openrewrite.java.JavaTypeVisitor.visit (JavaTypeVisitor.java:83)
    at org.openrewrite.java.tree.TypeUtils.resolveTypeParameters (TypeUtils.java:702)
    at org.openrewrite.java.tree.TypeUtils.maybeResolveParameters (TypeUtils.java:679)
    at org.openrewrite.java.tree.TypeUtils.isAssignableToFullyQualified (TypeUtils.java:604)
    at org.openrewrite.java.tree.TypeUtils.isAssignableToCore (TypeUtils.java:507)
    at org.openrewrite.java.tree.TypeUtils.isAssignableTo (TypeUtils.java:442)
    at org.openrewrite.java.tree.TypeUtils.isAssignableTo (TypeUtils.java:417)
    at software.amazon.awssdk.v2migration.S3StreamingResponseToV2$Visitor.visitMethodInvocation (S3StreamingResponseToV2.java:133)
    at software.amazon.awssdk.v2migration.S3StreamingResponseToV2$Visitor.visitMethodInvocation (S3StreamingResponseToV2.java:72)
    at org.openrewrite.java.tree.J$MethodInvocation.acceptJava (J.java:4275)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitRightPadded (JavaVisitor.java:1310)
    at org.openrewrite.java.JavaVisitor.visitMethodInvocation (JavaVisitor.java:870)
    at software.amazon.awssdk.v2migration.S3StreamingResponseToV2$Visitor.visitMethodInvocation (S3StreamingResponseToV2.java:115)
    at software.amazon.awssdk.v2migration.S3StreamingResponseToV2$Visitor.visitMethodInvocation (S3StreamingResponseToV2.java:72)
    at org.openrewrite.java.tree.J$MethodInvocation.acceptJava (J.java:4275)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitRightPadded (JavaVisitor.java:1310)
    at org.openrewrite.java.JavaVisitor.visitMethodInvocation (JavaVisitor.java:870)
    at software.amazon.awssdk.v2migration.S3StreamingResponseToV2$Visitor.visitMethodInvocation (S3StreamingResponseToV2.java:115)
    at software.amazon.awssdk.v2migration.S3StreamingResponseToV2$Visitor.visitMethodInvocation (S3StreamingResponseToV2.java:72)
    at org.openrewrite.java.tree.J$MethodInvocation.acceptJava (J.java:4275)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitReturn (JavaVisitor.java:1046)
    at org.openrewrite.java.tree.J$Return.acceptJava (J.java:5330)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitRightPadded (JavaVisitor.java:1310)
    at org.openrewrite.java.JavaVisitor.lambda$visitBlock$4 (JavaVisitor.java:392)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaVisitor.visitBlock (JavaVisitor.java:391)
    at org.openrewrite.java.tree.J$Block.acceptJava (J.java:833)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitMethodDeclaration (JavaVisitor.java:839)
    at org.openrewrite.java.tree.J$MethodDeclaration.acceptJava (J.java:4021)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitRightPadded (JavaVisitor.java:1310)
    at org.openrewrite.java.JavaVisitor.lambda$visitBlock$4 (JavaVisitor.java:392)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaVisitor.visitBlock (JavaVisitor.java:391)
    at org.openrewrite.java.tree.J$Block.acceptJava (J.java:833)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.visitClassDeclaration (JavaVisitor.java:474)
    at org.openrewrite.java.tree.J$ClassDeclaration.acceptJava (J.java:1370)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitAndCast (TreeVisitor.java:309)
    at org.openrewrite.java.JavaVisitor.lambda$visitCompilationUnit$9 (JavaVisitor.java:486)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.java.JavaVisitor.visitCompilationUnit (JavaVisitor.java:486)
    at org.openrewrite.java.tree.J$CompilationUnit.acceptJava (J.java:1652)
    at org.openrewrite.java.tree.J.accept (J.java:55)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:154)
    at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$7 (RecipeRunCycle.java:210)
    at org.openrewrite.table.RecipeRunStats$PhaseTimer.recordTimed (RecipeRunStats.java:142)
    at org.openrewrite.table.RecipeRunStats$RecipeTimers.recordEdit (RecipeRunStats.java:130)
    at org.openrewrite.table.RecipeRunStats.recordEdit (RecipeRunStats.java:58)
    at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$8 (RecipeRunCycle.java:206)
    at org.openrewrite.scheduling.RecipeStack.reduce (RecipeStack.java:60)
    at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$9 (RecipeRunCycle.java:179)
    at org.openrewrite.internal.InMemoryLargeSourceSet.lambda$edit$0 (InMemoryLargeSourceSet.java:86)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.internal.InMemoryLargeSourceSet.edit (InMemoryLargeSourceSet.java:85)
    at org.openrewrite.scheduling.RecipeRunCycle.editSources (RecipeRunCycle.java:177)
    at org.openrewrite.RecipeScheduler.runRecipeCycles (RecipeScheduler.java:84)
    at org.openrewrite.RecipeScheduler.scheduleRun (RecipeScheduler.java:41)
    at org.openrewrite.Recipe.run (Recipe.java:442)
    at org.openrewrite.Recipe.run (Recipe.java:438)
    at org.openrewrite.Recipe.run (Recipe.java:434)
    at org.openrewrite.maven.AbstractRewriteBaseRunMojo.runRecipe (AbstractRewriteBaseRunMojo.java:243)
    at org.openrewrite.maven.AbstractRewriteBaseRunMojo.listResults (AbstractRewriteBaseRunMojo.java:154)
    at org.openrewrite.maven.AbstractRewriteRunMojo.execute (AbstractRewriteRunMojo.java:66)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:126)
    at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute2 (MojoExecutor.java:328)
    at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute (MojoExecutor.java:316)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:212)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:174)
    at org.apache.maven.lifecycle.internal.MojoExecutor.access$000 (MojoExecutor.java:75)
    at org.apache.maven.lifecycle.internal.MojoExecutor$1.run (MojoExecutor.java:162)
    at org.apache.maven.plugin.DefaultMojosExecutionStrategy.execute (DefaultMojosExecutionStrategy.java:39)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:159)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:105)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:73)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:53)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:118)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:261)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:173)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:101)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:919)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:285)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:207)
    at jdk.internal.reflect.DirectMethodHandleAccessor.invoke (DirectMethodHandleAccessor.java:103)
    at java.lang.reflect.Method.invoke (Method.java:580)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:255)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:201)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:361)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:314)

@timtebeek
Copy link
Copy Markdown
Member

That helps, thanks! Here's a link to the relevant recipe:
https://github.com/aws/aws-sdk-java-v2/blob/d3c1e525d7c936d7b23c9878457c41761a9c0db3/v2-migration/src/main/java/software/amazon/awssdk/v2migration/S3StreamingResponseToV2.java#L110-L133

This could likely have been prevented had they used a precondition to limit which types get evaluated at all:
https://github.com/aws/aws-sdk-java-v2/blob/d3c1e525d7c936d7b23c9878457c41761a9c0db3/v2-migration/src/main/java/software/amazon/awssdk/v2migration/S3StreamingResponseToV2.java#L67-L70

Typically there would be some pattern like

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        return Preconditions.check(new UsesType<>(V1_S3_MODEL_PKG + "S3Object"), new Visitor());
    }

That way recipes get a nice performance boost, as they don't have to traverse files that have no relation whatsoever to the recipe, like in your case of a ElasticSearch builder being used presumably not immediately along side any S3Object usage.

@timtebeek
Copy link
Copy Markdown
Member

I've gone ahead and reverted the unwanted changes, and based on your intended usage adapted the JavaTypeVisitor used in TypeUtils.resolveTypeParameters to avoid the overflow there (only). That does align with that you were expecting?

And since it sounds like you were already testing with a snapshot version, can you confirm this fixes the issue for you too?

@mcebanupgrade
Copy link
Copy Markdown
Contributor Author

hi @timtebeek !
I just tried out your simplified fix and indeed, it fixes the issue we were having.
Thanks for taking care of this

@timtebeek timtebeek marked this pull request as ready for review January 8, 2026 16:35
@timtebeek timtebeek moved this from In Progress to Ready to Review in OpenRewrite Jan 8, 2026
@timtebeek
Copy link
Copy Markdown
Member

Thanks for validating! @sambsnyd are you ok with the changes here to only change the usage in TypeUtils as you described?

@timtebeek timtebeek requested a review from sambsnyd January 8, 2026 16:38
@sambsnyd sambsnyd merged commit 9029c84 into openrewrite:main Jan 15, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Jan 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working java

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

StackOverflowError in JavaTypeVisitor when processing circular type references

3 participants