Skip to content

[Regression jdt.ls 1.59.0] ECJ (Eclipse 4.41 I-build) incorrectly infers Object/wildcard capture for unconstrained generic return in if/else lambda branches (Reactor Mono.empty(), Mono.error() patterns) #3831

Description

@roneiv

Since upgrading to vscode-java 1.55.0 (jdt.ls 1.59.0, Eclipse 4.41 I-builds ECJ), the language server incorrectly flags valid Java code that compiles cleanly under javac (Maven). The regression was not present in vscode-java 1.54.0 (jdt.ls 1.58.0, Eclipse 4.40 I-builds).

Root cause area: ECJ fails to propagate the target type from a flatMap/concatMap lambda's return type through an if/else where one branch returns a typed concrete Mono and the other branch returns an unconstrained generic expression (Mono.empty(), Mono.error(), or a generic Mono createMono() factory method). Instead of inferring T from the sibling branch, ECJ infers Object or a wildcard lower-bound capture, causing a spurious type-mismatch error downstream.

Minimal reproducer (compiles clean under javac/Maven, errors in JDT 1.59.0):

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class InferenceRegression {

    // Pattern 1: Mono.empty() in else branch — ECJ infers Flux<Object>
    // Error: "Type mismatch: cannot convert from Flux<Object> to Flux<String>"
    public Flux<String> pattern1(boolean condition, String value) {
        return Mono.just(value)
            .flatMap(v -> {
                if (condition) {
                    return Mono.just(v);
                } else {
                    return Mono.empty(); // ECJ infers Mono<Object>, should be Mono<String>
                }
            })
            .flux();
    }

    // Pattern 2: Mono.error() via defer in try/catch — ECJ infers wrong capture
    // Error: "Cannot infer type argument(s) for collectMap(...)"
    public Mono<java.util.Map<String, String>> pattern2(java.util.List<String> items) {
        return Flux.fromIterable(items)
            .concatMap(item -> {
                try {
                    return Flux.just(item);
                } catch (RuntimeException e) {
                    return Flux.defer(() -> Flux.error(e)); // ECJ loses target type
                }
            })
            .collectMap(s -> s, s -> s);
    }

    // Pattern 3: Unconstrained generic factory in nested flatMap branch
    // Error: "Type mismatch: cannot convert from Mono<capture-of ? super T> to Mono<String>"
    public Mono<String> pattern3(boolean flag, String value) {
        return Mono.just(value)
            .flatMap(v -> {
                if (flag) {
                    return Mono.just(v);
                } else {
                    return genericErrorFactory(); // <T> Mono<T> factory, ECJ loses T=String
                }
            });
    }

    private <T> Mono<T> genericErrorFactory() {
        return Mono.error(new RuntimeException("error"));
    }
}

Expected: No errors (compiles with mvn clean install).

Actual: JDT Language Server 1.59.0 reports type-mismatch errors on the return statement of the enclosing method.

Fix/workaround: Adding an explicit type witness resolves each case:

Mono.<String>empty()
Flux.<String>error(e)

Alternatively downgrade to jdt.ls 1.58.0 (In VSCode force vscode-java 1.54.0)

Environment:
vscode-java: 1.55.0 (broken) vs 1.54.0 (works)
jdt.ls: 1.59.0 (ECJ from eclipse/updates/4.41-I-builds/) vs 1.58.0 (ECJ from eclipse/updates/4.40-I-builds/)
Java: OpenJDK 21 (Temurin)
OS: Windows 11
Maven: builds successfully (javac unaffected)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions