11

I'm migrating a project from JAVA 8 to JAVA 9 and I'm having some trouble getting the code to work. All work in JAVA 8 but in 9 I'm having the following errors:

Error java: reference to ok is ambiguous
      both method <T>ok(java.util.function.Supplier<T>)  and method ok(web.Procedure) match

here is the code when I'm calling the method:

public ResponseEntity<List<MailTemplateDto>> mailTemplateFindAll() {
    return ok(() -> mailTemplateService.findAll());
}

and here is the implementation :

 public <T> ResponseEntity<T> ok(Supplier<T> action) {
     return this.body(HttpStatus.OK, action);
 }

 public <T> ResponseEntity<T> ok(T body) {
     return this.ok(() -> {
         return body;
     });
 }

 public ResponseEntity<Void> ok(Procedure action) {
     action.invoke();
     return this.status(HttpStatus.OK);
 }

 public ResponseEntity<Void> ok() {
     return this.status(HttpStatus.OK);
 }

code for Procedure interface:

@FunctionalInterface
public interface Procedure {
    void invoke();
}

Any ideas?


Reproducible Code ::

public class Q48227496 {

    public A<?> test() {
        return ok(() -> System.out.append("aaa"));
    }

    private class A<T> {
    }

    private <T> A<T> ok(java.util.function.Supplier<T> action) {
        return new A<>();
    }

    public <T> A<T> ok(T body) {
        return new A<>();
    }

    private <T> A<T> ok(Procedure action) {
        return new A<>();
    }

    public <T> A<T> ok() {
        return new A<>();
    }

    @FunctionalInterface
    public interface Procedure {
        void invoke();
    }
}

Resulting in the following error with compiler::

error: reference to ok is ambiguous
        return ok(() -> System.out.append("aaa"));
               ^
  both method <T#1>ok(Supplier<T#1>) in Q48227496 and method <T#2>ok(Procedure) in Q48227496 match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>ok(Supplier<T#1>)
    T#2 extends Object declared in method <T#2>ok(Procedure)
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 1
    here is a reproduce of the error https://github.com/alehyen/demo but if you change the code in Controller.java to @RequestMapping(method = RequestMethod.GET) public ResponseEntity userGet() { return ok(() -> "Hello World"); } it'll work – Ismail Alehyen Jan 12 '18 at 17:40
  • 1
    The problem in Java 9 is because there are 3 one-arg `ok` methods to choose from (the first three ones of the question). If you comment out one of them, the compiler will happily choose one among the other two, presumably based on JLS rules. It never chooses the one that receives `T body`, and when that one is commented, it goes for the one that receives a `Supplier`. As to why this works in Java 8 but not in Java 9, I have no clue... I don't even dare to guess which is the correct behavior, according to JLS... – fps Jan 12 '18 at 19:34
  • @FedericoPeraltaSchaffner they've changed the JLS in this part – Andremoniy Jan 12 '18 at 21:19
  • 1
    @FedericoPeraltaSchaffner of course this is related to the special void compatibility rule and the fact that an expression can be used as a statement (https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.8), but boy this is complicated (and misleading!) – Eugene Jan 13 '18 at 19:24
  • 1
    @Eugene I think it's a compiler bug w.r.t. the rules of precedence for targeting overloaded methods. And I'm sure it's a bug because it works for two methods but not for three, no matter what the two method combination is. If it weren't a bug, we should be able to find the pair of methods that produce the ambiguity. – fps Jan 13 '18 at 19:45
  • @FedericoPeraltaSchaffner you are right, I was playing with this so much (too much?). I get the weird behavior if I replace one of the methods like this: `public A> test() { return ok(() -> { System.out.append("aaa"); return; }); }` – Eugene Jan 13 '18 at 21:57
  • 6
    @Eugene note that you don’t need a return statement to turn an ExpessionStatement into a Statement; curly braces are enough. You may memorize it as the bracket disambiguation: `args -> { exprStmt; }` makes it a statement and `args -> ( exprStmt )` makes it an expression. Otherwise, the method accepting a non-`void` function should win. Funny enough, Java 9’s compiler fully agrees with it, [it just doesn’t like the method order](https://stackoverflow.com/a/48260046/2711488)… – Holger Jan 15 '18 at 09:45
  • I can confirm that the bug JDK-8195598 has been resolved with Java 11(build 11-ea+23). – Naman Aug 19 '18 at 08:53

1 Answers1

8

This is a bug.

It has been reported with bug ID : JDK-8195598


I simplified your example further:

public class Q48227496 {
    public CompletableFuture<?> test() {
        return ok(() -> System.out.append("aaa"));
    }
    public <T> CompletableFuture<T> ok(Supplier<T> action) {
        return CompletableFuture.supplyAsync(action);
    }
    public <T> CompletableFuture<T> ok(T body) {
        return CompletableFuture.completedFuture(body);
    }
    public CompletableFuture<Void> ok(Runnable action) {
        return CompletableFuture.runAsync(action);
    }
}

This fails in the release version of Java 9 with “reference to ok is ambiguous”, stating “both method <T>ok(Supplier<T>) in Q48227496 and method ok(Runnable) in Q48227496 match”.

But just changing the order of the methods

public class Q48227496 {
    public CompletableFuture<?> test() {
        return ok(() -> System.out.append("aaa"));
    }
    public <T> CompletableFuture<T> ok(T body) {
        return CompletableFuture.completedFuture(body);
    }
    public <T> CompletableFuture<T> ok(Supplier<T> action) {
        return CompletableFuture.supplyAsync(action);
    }
    public CompletableFuture<Void> ok(Runnable action) {
        return CompletableFuture.runAsync(action);
    }
}

causes the compiler to accept the code without any errors.

So, obviously, this is a compiler bug as the order of the method declarations should never have an impact on the validity of the code.

Also, removing the ok(T) method makes the code accepted.

Note that whenever the compiler accepts the code, it considers ok(Supplier) to be more specific than ok(Runnable), which is the expected behavior for a function parameter that matches both.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Nice observation about the order of methods! – Andremoniy Jan 15 '18 at 10:07
  • @Holger or just add a cast in the function in the lamba expression to it's type of return (for example if it's a method that return a string doing : public A> test() { return ok(() ->(String)getString()); }) will work without changing the order of the methods – Ismail Alehyen Jan 15 '18 at 10:40
  • 5
    @IsmailAlehyen as said [here](https://stackoverflow.com/questions/48227496/reference-to-method-is-ambiguous-when-migrating-from-java8-to-java9/48260046#comment83503368_48227496), you can control the target method just by brackets, i.e. `public A> test() { return ok(() -> (getString()) ); })` is already sufficient to unambiguously invoke the method expecting a `Supplier` (because `(expression)` is not a statement, whereas `public A> test() { return ok(() -> { getString()); } })` will always invoke the method expecting `Procedure`, as `{ statement; }` (without `return`) is not an expression. – Holger Jan 15 '18 at 10:46
  • 2
    Just fixed it in the latest jdk/jdk: http://hg.openjdk.java.net/jdk/jdk/rev/db044d7e9885 Thanks for the report. – Maurizio Cimadamore Jan 18 '18 at 13:10