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 settings — BadImport is on by default, so no
configuration is needed to hit it.
Three files, no dependencies beyond Error Prone itself:
repro/model/Outer.java
package repro.model;
public class Outer {
public enum Key {
A
}
}
repro/TypeUse.java
package repro;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
public @interface TypeUse {}
repro/Repro.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):
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) |
CRASH — SourcePositionException: invalid source position: [67, -1) |
| 2.49.0 |
CRASH — SourcePositionException |
| 2.48.0 |
CRASH — SourcePositionException |
| 2.47.0 |
CRASH — IllegalArgumentException: invalid replacement: [67, -1) |
| 2.46.0 |
CRASH — IllegalArgumentException |
| 2.45.0 |
CRASH — IllegalArgumentException |
| 2.44.0 |
CRASH — IllegalArgumentException |
| 2.43.0 |
CRASH — IllegalArgumentException |
| 2.42.0 |
CRASH — IllegalArgumentException |
| 2.41.0 |
CRASH — IllegalArgumentException |
| 2.40.0 |
CRASH — IllegalArgumentException |
| 2.39.0 |
CRASH — IllegalArgumentException |
| 2.38.0 |
CRASH — IllegalArgumentException |
| 2.37.0 |
CRASH — IllegalArgumentException |
| 2.36.0 |
CRASH — IllegalArgumentException |
| 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.
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?
BadImportcrashes withSourcePositionException(aborting the whole compilation) when thebad-named nested class it wants to qualify appears as the type of a record component that also
carries an annotation whose
@TargetincludesElementType.TYPE_USE.This reproduces with default Error Prone settings —
BadImportis on by default, so noconfiguration is needed to hit it.
Three files, no dependencies beyond Error Prone itself:
repro/model/Outer.javarepro/TypeUse.javarepro/Repro.javaCompile (JDK 25;
$EP_CPis theerror_prone_core:2.50.0runtime classpath):What did you expect to see?
The ordinary
BadImportfinding with a working suggested fix, exactly as produced when theannotation is removed:
What did you see instead?
Narrowing
Only the combination record component + TYPE_USE-targeted annotation crashes. Every other
shape of the same declaration produces a correct suggested fix:
Further observations:
:WARN, and at:ERROR.@TypeUse Key keyand@TypeUse Map<Key, String> keycrash identically.
67above) is the offset of the@of@TypeUse, not of theKeyidentifier at offset76. So the replacement region is being computed over theAnnotatedTypenode wrapping the component's type rather than over the identifier itself, andthat node has no end position recorded (
-1) — presumably because the record component's typetree 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.
SourcePositionException: invalid source position: [67, -1)SourcePositionExceptionSourcePositionExceptionIllegalArgumentException: invalid replacement: [67, -1)IllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionIllegalArgumentExceptionNoSuchFieldError: Class com.sun.tools.javac.code.TypeTag does not have member field 'UNKNOWN'fromASTHelpers.<clinit>)Two notes on reading that table:
same
Replacement.createcall site with the identical offsets[67, -1).SourcePositionExceptionis a wrapper introduced around the pre-existingPreconditions.checkArgumentfailure.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
BadImportis the onlyoption.
Impact
Because an annotation with a
TYPE_USEtarget is enough, this is easy to hit in ordinary code.jakarta.validation.constraints.@NotNull(and the rest of Bean Validation) listsTYPE_USEamongits 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 DTOmade
BadImportunusable repository-wide, and there is no per-site suppression that helps becausethe 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 asIllegalArgumentException, and 2.35.1 and older report it as a generic unhandled plugin exception.SourcePositionException: invalid source position: [67, -1)IllegalArgumentException: invalid replacement: [67, -1)Suggested direction
Either skip the fix (emit the finding without a
SuggestedFix) when the end position of theusage's enclosing tree is unavailable, or resolve the replacement region from the identifier node
rather than from its enclosing
AnnotatedType.