eclipse-jdt / eclipse-jdt/eclipse.jdt.core

NPE in MessageSend.analyseCode (MethodBinding.isStatic, binding null) with null analysis enabled, on generic method inherited from two interfaces

Open
#5,387 0 comments 0 reactions 1 assignee Claimed by @stephan-herrmann View on GitHub
Dominant language
Java
Stars
237
Forks
195
Avg merge
1d 12h
Merged PRs (30d)
47

Description

# NPE in MessageSend.analyseCode (MethodBinding.isStatic, binding null) with null analysis enabled, on generic method inherited from two interfaces

## Summary

With `org.eclipse.jdt.core.compiler.annotation.nullanalysis` enabled, calling a generic method that resolves to an override-equivalent method inherited from two separate super-interfaces (here, `org.hibernate.Session.createNamedQuery`, inherited from both `javax.persistence.EntityManager` and `org.hibernate.query.QueryProducer`) with an array `Class` witness (e.g. `Object[].class`) crashes both the batch build and the live reconciler with:

```
java.lang.NullPointerException: Cannot invoke "org.eclipse.jdt.internal.compiler.lookup.MethodBinding.isStatic()" because "this.binding" is null
at org.eclipse.jdt.internal.compiler.ast.MessageSend.analyseCode(MessageSend.java:123)
at org.eclipse.jdt.internal.compiler.ast.LocalDeclaration.analyseCode(LocalDeclaration.java:108)
at org.eclipse.jdt.internal.compiler.ast.MethodDeclaration.analyseCode(MethodDeclaration.java:160)
at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.internalAnalyseCode(TypeDeclaration.java:887)
at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.analyseCode(TypeDeclaration.java:316)
at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.analyseCode(CompilationUnitDeclaration.java:127)
```

Real `javac`, and a plain `ecj` batch compile of the same source with the same flags, both compile this file without error or warning. Only the JDT compiler's live/incremental path (both the Java Builder full-build and the reconciler's live problem-detection pass) crashes.

## Bisection

This is a regression between the ECJ batch-compiler builds bundled in `redhat.java` extension versions 1.55.0 and 1.56.0:

- **Working**: `org.eclipse.jdt.core.compiler.batch_3.46.100.v20260624-0231.jar` (bundled in redhat.java 1.55.0)
- **Broken**: `org.eclipse.jdt.core.compiler.batch_3.46.100.v20260826-1225.jar` (bundled in redhat.java 1.56.0, still present in 1.57.x)

Diffing the decompiled bytecode of `MessageSend.class` between these two jars shows the only change in the class is inside `resolveType(BlockScope)`, where a new call to `binding.typeVariables()` was added to the branch condition that decides whether to convert the resolved return type to its raw erasure - previously that method was only read via a `getfield` deeper in the same method. `MethodBinding.class` also changed between the two builds (though `isStatic()` and `typeVariables()` themselves are byte-identical). `analyseCode()` and `LocalDeclaration.class` are byte-identical between the two builds, so the actual point of divergence is in resolution, not in the flow-analysis code that ultimately crashes.

Separately, this reproduces the shape reported in eclipse-jdt/eclipse.jdt.core#5249 / redhat-developer/vscode-java#4500 - both trace back to a July 27, 2026 change (commit 62bd2a3, PR #5237) to how null analysis merges override-equivalent methods inherited from multiple interfaces (`NullAnnotationMatching.methodWithMergedNullAnnotations`, `MethodBinding.copy`). PR #5373 (open as of 2026-09-08) fixes the "wrong merged return type" variant of this same regression, but does not cover this NullPointerException.

## Minimal reproduction

[jdtls-null-analysis-npe-repro.zip](https://github.com/user-attachments/files/32040500/jdtls-null-analysis-npe-repro.zip)

Two public Maven Central dependencies, no project-specific code:

`pom.xml`:
```xml

org.hibernate
hibernate-core
5.6.15.Final

io.dropwizard
dropwizard-core
3.0.17

```

`.vscode/settings.json` (or equivalent `org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled`):
```json
{
"java.compile.nullAnalysis.mode": "automatic"
}
```

`src/main/java/repro/Repro.java`:
```java
package repro;

import org.hibernate.Session;
import org.hibernate.query.Query;

public class Repro {
void test(Session session) {
Query query = session.createNamedQuery("x", Object[].class);
}
}
```

Notes on what we ruled out while narrowing this down (in case it saves someone else the trip):

- Not Lombok-related - an earlier, much larger reproduction attempt involved Lombok and a `Collectors.toMap` lambda; neither was necessary once we identified the actual trigger.
- Not scale-dependent - reproduces identically whether the project has 1 source file or 400+.
- `hibernate-core` alone (without `dropwizard-core`) does **not** reproduce it, even though `Session` already extends both `EntityManager` and `QueryProducer` on its own. Something `dropwizard-core` brings onto the classpath is necessary alongside the Hibernate diamond, though we did not bisect further into which specific transitive dependency of `dropwizard-core` is responsible.
- `Object.class` instead of `Object[].class` as the `Class` witness does not reproduce it - the array-type witness specifically is required.

## Environment

- Reproduced via the `redhat.java` VS Code extension, versions 1.56.0 and 1.57.2026090408
- Both `-11` and default source/target levels reproduce it
- Reproduces both via the Java Builder full build and the live reconciler (background problem detection on file open, no save required)

## Expected behavior

The file compiles without error or warning, matching real `javac` and a plain `ecj` batch compile of the same source.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.