BadImport: SourcePositionException building the suggested fix for a record component with a TYPE_USE annotation

Author: markusbaumgCreated Aug 28, 2026Updated Aug 28, 2026

What version of Error Prone are you using?

2.50.0

Does this issue reproduce with the latest release?

Yes. 2.50.0 is the latest release at time of writing, and the crash is present in every release back to 2.36.0 — see the version table below. It is not a regression.

What did you do?

BadImport crashes with SourcePositionException (aborting the whole compilation) when the bad-named nested class it wants to qualify appears as the type of a record component that also carries an annotation whose @Target includes ElementType.TYPE_USE.

This reproduces with default Error Prone settingsBadImport is on by default, so no configuration is needed to hit it.

Three files, no dependencies beyond Error Prone itself:

repro/model/Outer.java

java
package repro.model;

public class Outer {
  public enum Key {
    A
  }
}

repro/TypeUse.java

java
package repro;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE_USE)
public @interface TypeUse {}

repro/Repro.java

java
package repro;

import repro.model.Outer.Key;

public record Repro(@TypeUse Key key) {}

Compile (JDK 25; $EP_CP is the error_prone_core:2.50.0 runtime classpath):

bash
javac \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
  -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
  -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \
  -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
  -XDcompilePolicy=simple \
  --should-stop=ifError=FLOW \
  -processorpath "$EP_CP" \
  '-Xplugin:ErrorProne' \
  -d out \
  repro/model/Outer.java repro/TypeUse.java repro/Repro.java

What did you expect to see?

The ordinary BadImport finding with a working suggested fix, exactly as produced when the annotation is removed:

Repro.java:5: warning: [BadImport] Importing nested classes/static methods/static fields with
commonly-used names can make code harder to read [...] Here we recommend using qualified class:
Outer.
public record Repro(Key key) {}
                    ^
    (see https://errorprone.info/bugpattern/BadImport)
  Did you mean 'public record Repro(Outer.Key key) {}'?

What did you see instead?

Repro.java:5: error: An unhandled exception was thrown by the Error Prone static analysis plugin.
public record Repro(@TypeUse Key key) {}
                    ^
     error-prone version: 2.50.0
     BugPattern: BadImport
     Stack Trace:
     com.google.errorprone.SourcePositionException: invalid source position: [67, -1)
        at com.google.errorprone.fixes.Replacement.create(Replacement.java:39)
        at com.google.errorprone.fixes.SuggestedFix$ReplacementFix.getReplacement(SuggestedFix.java:477)
        at com.google.errorprone.fixes.SuggestedFix.getReplacements(SuggestedFix.java:95)
        at com.google.errorprone.fixes.AppliedFix.apply(AppliedFix.java:45)
        at com.google.errorprone.JavacErrorDescriptionListener.lambda$new$0(JavacErrorDescriptionListener.java:94)
        at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1229)
        at com.google.errorprone.JavacErrorDescriptionListener.lambda$new$1(JavacErrorDescriptionListener.java:90)
        at com.google.errorprone.JavacErrorDescriptionListener.lambda$onDescribed$3(JavacErrorDescriptionListener.java:109)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
        at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:197)
        at java.base/java.util.Collections$2.tryAdvance(Collections.java:5182)
        at java.base/java.util.Collections$2.forEachRemaining(Collections.java:5190)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:723)
        at com.google.errorprone.JavacErrorDescriptionListener.onDescribed(JavacErrorDescriptionListener.java:111)
        at com.google.errorprone.ErrorProneAnalyzer.lambda$finished$3(ErrorProneAnalyzer.java:216)
        at com.google.errorprone.VisitorState.reportMatch(VisitorState.java:248)
        at com.google.errorprone.scanner.Scanner.reportMatch(Scanner.java:126)
        at com.google.errorprone.scanner.ErrorProneScanner.processMatchers(ErrorProneScanner.java:540)
        at com.google.errorprone.scanner.ErrorProneScanner.visitImport(ErrorProneScanner.java:804)
        at com.google.errorprone.scanner.ErrorProneScanner.visitImport(ErrorProneScanner.java:180)
        at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCImportBase.accept(JCTree.java:678)
        at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
        at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
        at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
        at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:110)
        at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:118)
        at jdk.compiler/com.sun.source.util.TreeScanner.visitCompilationUnit(TreeScanner.java:150)
        at com.google.errorprone.scanner.ErrorProneScanner.visitCompilationUnit(ErrorProneScanner.java:663)
        at com.google.errorprone.scanner.ErrorProneScanner.visitCompilationUnit(ErrorProneScanner.java:180)
        at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCCompilationUnit.accept(JCTree.java:627)
        at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:66)
        at com.google.errorprone.scanner.Scanner.scan(Scanner.java:58)
        at com.google.errorprone.scanner.ErrorProneScannerTransformer.apply(ErrorProneScannerTransformer.java:43)
        at com.google.errorprone.ErrorProneAnalyzer.finished(ErrorProneAnalyzer.java:231)
        at jdk.compiler/com.sun.tools.javac.api.MultiTaskListener.finished(MultiTaskListener.java:133)
        at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1423)
        at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1370)
        at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:955)
        at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:319)
        at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:178)
        at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:66)
        at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:52)
1 error

Narrowing

Only the combination record component + TYPE_USE-targeted annotation crashes. Every other shape of the same declaration produces a correct suggested fix:

declaration result
record component + TYPE_USE-targeted annotation CRASH
record component, no annotation finding only, fix is clean
record component + FIELD / PARAMETER / METHOD / RECORD_COMPONENT-only annotation finding only, fix is clean
class field / method parameter / return type / local + TYPE_USE annotation finding only, fix is clean

Further observations:

  • Severity is irrelevant: it crashes at default severity, at :WARN, and at :ERROR.
  • The generic wrapper is irrelevant: @TypeUse Key key and @TypeUse Map<Key, String> key crash identically.
  • The reported start offset (67 above) is the offset of the @ of @TypeUse, not of the Key identifier at offset 76. So the replacement region is being computed over the AnnotatedType node wrapping the component's type rather than over the identifier itself, and that node has no end position recorded (-1) — presumably because the record component's type tree is reached through a synthesized member rather than the original source tree.

Is this a regression?

No — it is not a regression. It reproduces on every Error Prone release I could run on JDK 25: 2.36.0 through 2.50.0, 15 consecutive releases spanning 2024-11-19 to 2026-06-10.

Error Prone result
2.50.0 (latest) CRASHSourcePositionException: invalid source position: [67, -1)
2.49.0 CRASHSourcePositionException
2.48.0 CRASHSourcePositionException
2.47.0 CRASHIllegalArgumentException: invalid replacement: [67, -1)
2.46.0 CRASHIllegalArgumentException
2.45.0 CRASHIllegalArgumentException
2.44.0 CRASHIllegalArgumentException
2.43.0 CRASHIllegalArgumentException
2.42.0 CRASHIllegalArgumentException
2.41.0 CRASHIllegalArgumentException
2.40.0 CRASHIllegalArgumentException
2.39.0 CRASHIllegalArgumentException
2.38.0 CRASHIllegalArgumentException
2.37.0 CRASHIllegalArgumentException
2.36.0 CRASHIllegalArgumentException
2.35.1 and older not testable — Error Prone does not run on JDK 25 (NoSuchFieldError: Class com.sun.tools.javac.code.TypeTag does not have member field 'UNKNOWN' from ASTHelpers.<clinit>)

Two notes on reading that table:

  • The change of exception type at 2.48.0 is cosmetic, not behavioural: both are thrown from the same Replacement.create call site with the identical offsets [67, -1). SourcePositionException is a wrapper introduced around the pre-existing Preconditions.checkArgument failure.
  • The floor at 2.35.1 is a JDK 25 incompatibility in Error Prone itself, unrelated to this bug. I had only JDK 25 available, so I cannot say whether the crash predates 2.36.0 — only that it has been present in every release since.

Consequently there is no "pin to an older version" workaround; disabling BadImport is the only option.

Impact

Because an annotation with a TYPE_USE target is enough, this is easy to hit in ordinary code. jakarta.validation.constraints.@NotNull (and the rest of Bean Validation) lists TYPE_USE among its targets, so any validated record DTO whose component type is an imported nested class named Builder / Key / Type / Id / Factory / … aborts the build. In our case a single such DTO made BadImport unusable repository-wide, and there is no per-site suppression that helps because the crash happens while rendering the fix for the import statement.

Version history

Not a regression — it crashes in every release I tested, back to 2.30.0. Only the failure mode changed: 2.36.0 introduced SourcePositionException, before that the same bad region surfaced as IllegalArgumentException, and 2.35.1 and older report it as a generic unhandled plugin exception.

Error Prone Result Reported as
2.50.0 - 2.48.0 CRASH SourcePositionException: invalid source position: [67, -1)
2.47.0 - 2.36.0 CRASH IllegalArgumentException: invalid replacement: [67, -1)
2.35.1 - 2.30.0 CRASH unhandled exception thrown by the Error Prone plugin

Suggested direction

Either skip the fix (emit the finding without a SuggestedFix) when the end position of the usage's enclosing tree is unavailable, or resolve the replacement region from the identifier node rather than from its enclosing AnnotatedType.