3

I'm getting a java.lang.NoSuchMethodError trying to invoke a Java method. As far as I can tell, the classpath is identical at compile-time and runtime-time so this error should not be occurring.

Repro steps:

  1. Create 4 Maven projects:

    • MyLibrary
    • ExtensionPresent
    • ExtensionMissing
    • UserCode
  2. Modules ExtensionPresent and ExtensionMissing export the same module name.

  3. ExtensionMissing exports:

package dummy;

public class Extension {}
  1. ExtensionPresent exports:
package dummy;

public class Extension {

    public static void present() {
        System.out.println("Extension present!");
    }
}
  1. MyLibrary declares ExtensionMissing as a dependency.
  2. UserCode declares MyLibrary as a dependency.
  3. UserCode.main() invokes Extension.present(). This triggers a compile-time error because ExtensionMissing does not contain this method.
  4. Now for the interesting part... In the UserCode project, add ExtensionPresent as a dependency after MyLibrary.
  5. I no longer get a compiler error (the method is now present at compile-time).
  6. When I try invoking UserCode.main() I get:
--- exec-maven-plugin:1.6.0:exec (default-cli) @ mavenproject3 ---
Exception in thread "main" java.lang.NoSuchMethodError: dummy.Extension.present()V

Is this a bug in my project configuration, Maven's implementation, or the JDK tools?

(On a side-note, I am doing this in an attempt to solve: Implementing a (compile-time) plugin architecture without split packages)

UPDATE: Here is an executable testcase: https://github.com/cowwoc/exec-maven-plugin-class-shadowing

Gili
  • 86,244
  • 97
  • 390
  • 689
  • 1
    *export the same module name.* do you by any chance mean exports the packages of same name? or do they have same **module name**? I guess the usage of `exports` is contradictory to the Java9 convention in the question. – Naman Dec 05 '17 at 10:16
  • @nullpointer The same module name, the same package name, the same class name. The only difference is the presence or absence of a method. – Gili Dec 05 '17 at 10:17
  • What are your eventual `javac`(during compilation) and `java`(during execution) commands, could you update the question with them please? Just to make sure this is not a duplicate of [Java 9: Possible to have 2 modules with same name on module path](https://stackoverflow.com/questions/46573572/java-9-possible-to-have-2-modules-with-same-name-on-module-path) – Naman Dec 05 '17 at 10:27
  • @nullpointer I've updated the question with the information you requested. – Gili Dec 05 '17 at 10:46
  • You could try running `mvn dependency:tree` to get effective list of dependencies used. Also, do you have different behaviour when invoking `UserCode` from command line `java -jar ...`? – scrutari Dec 05 '17 at 20:31
  • @scrutari `mvn dependency:tree` gives back the expected result. Running the project using `java -cp ...` works perfectly if I swap the order of `ExtensionPresent`, `ExtensionMissing` dependencies. – Gili Dec 06 '17 at 01:39
  • @Gili Looking at those command..Is the use of classpath while compilation and only modulepath during execution not a possible cause of the difference in the behavior? – Naman Dec 06 '17 at 02:28
  • @nullpointer I don't think it would make a difference in this case. More importantly, notice how the order of `ExtensionPresent` and `ExtensionMissing` is swapped between the compiler and exec plugin. The compiler plugin does it right. The exec plugin lists them in the wrong order. If I invoke `java` manually with the dependencies swapped the program runs just fine. I just updated the question with a link to an executable testcase. Please try it out. – Gili Dec 06 '17 at 03:01
  • Just for info, executing `mvn clean install` on the cloned test case of yours fails as well. *Compilation failure `[ERROR] ../exec-maven-plugin-class-shadowing/UserCode/src/main/java/testcase/Main.java:[8,18] cannot find symbol [ERROR] symbol: method present() [ERROR] location: class extension.Extension`* – Naman Dec 06 '17 at 03:59
  • @nullpointer That's odd, not here. I am using Maven 3.5.2, Oracle JDK 9.0.1. How about you? – Gili Dec 06 '17 at 04:12
  • @Gili Same versions. *Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T13:28:13+05:30) Maven home: /usr/local/Cellar/maven/3.5.2/libexec Java version: 9.0.1, vendor: Oracle Corporation Java home: /Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home Default locale: en_US, platform encoding: UTF-8 OS name: "mac os x", version: "10.10.5", arch: "x86_64", family: "mac"* – Naman Dec 06 '17 at 04:13
  • @nullpointer This is wild. I can reproduce the failure you mentioned but if I move the project folder to a different directory the build works. This is 100% reproducible too. Can you please pull the repository again and tell me what is the difference between `good.txt` and `bad.txt`? These are the build logs in the two different directories. I can see `maven-compiler-plugin` using the wrong dependency order in `bad.txt` but I can't figure out why changing the directory would do that. – Gili Dec 06 '17 at 04:54
  • Not very certain about the difference on your machine. I can reproduce the failure on any directory location on mine. – Naman Dec 06 '17 at 05:06
  • @nullpointer I can't figure out why `maven-compiler-plugin` does the wrong thing on all directories except for the one mentioned in `good.txt` but regardless... According to https://stackoverflow.com/a/793193/14731 all Maven plugins are supposed to respect the classpath ordering as laid out in `pom.xml`. They are not. This sounds like a bug, does it not? – Gili Dec 06 '17 at 05:43
  • Not very sure of the order of execution persistence in the current maven version. But could possibly be an issue. Since the order would matter when the dependencies are resolved in the modulepath when you have two modules with same name in the the graph. – Naman Dec 06 '17 at 05:55
  • @nullpointer So it turns out that I wasn't crazy. The behavior really does change depending on which directory the project is located in (due to the use of a `HashMap`). Also, this is now a confirmed bug. See https://issues.apache.org/jira/browse/MCOMPILER-317?focusedCommentId=16280827&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-16280827 – Gili Dec 07 '17 at 02:58

1 Answers1

0

When I enable debug logging, I see the plugins firing the following commands:

  • compiler-maven-plugin is invoking: javac -d ~/UserCode/target/classes -classpath ~/UserCode/target/classes: --module-path ~/.m2/repository/ExtensionPresent/1.0-SNAPSHOT/ExtensionPresent-1.0-SNAPSHOT.jar:~/.m2/repository/MyLibrary/1.0-SNAPSHOT/MyLibrary-1.0-SNAPSHOT.jar:~/.m2/repository/ExtensionMissing/1.0-SNAPSHOT/ExtensionMissing-1.0-SNAPSHOT.jar: -sourcepath ~/UserCode/src/main/java:~/UserCode/target/generated-sources/annotations: -s ~/UserCode/target/generated-sources/annotations -g -nowarn -target 9 -source 9 -encoding UTF-8
  • maven-exec-plugin is invoking java, --module-path, ~/UserCode/target/classes:~/.m2/repository/MyLibrary/1.0-SNAPSHOT/MyLibrary-1.0-SNAPSHOT.jar:~/.m2/repository/ExtensionMissing/1.0-SNAPSHOT/ExtensionMissing-1.0-SNAPSHOT.jar:~/.m2/repository/ExtensionPresent/1.0-SNAPSHOT/ExtensionPresent-1.0-SNAPSHOT.jar, -m, UserCode/com.usercode.Main]

This led me to believe that maven-exec-plugin is listing the dependencies in the wrong order. I tried invoking the application manually, swapping the order of dependencies, and sure enough the program runs.

I filed a bug report against maven-exec-plugin.

I filed a second bug report against maven-compiler-plugin because it seems that it also passes the wrong order to javac depending on the name of the project directory (odd but true).

Gili
  • 86,244
  • 97
  • 390
  • 689
  • 1
    The root problem is that duplicate modules are allowed across different entries on the module paths and that Maven creates an entry for each dependency. This means we're back in the situation that the order on the command line actually matters (just like in JAR hell). – Nicolai Parlog Dec 06 '17 at 08:16
  • @Nicolai Order *should* matter (it's a feature, not a bug). There are legitimate reasons for needing to override resources and classes. See https://issues.apache.org/jira/browse/MNG-1412 for why others wanted it as well. – Gili Dec 06 '17 at 08:37
  • 1
    I'm not saying it's bug, just that Maven's behavior undermines reliable configuration (a lack thereof is exactly the reason for the problem). This is likely a good trade-off, but it can still have unfortunate effects. – Nicolai Parlog Dec 06 '17 at 13:36
  • @Nicolai How does Maven's behavior undermine reliable configuration? Retaining iteration order actually **improves** the repeatability of builds. – Gili Dec 06 '17 at 21:20
  • Depending on iteration order is inherently unreliable (as you've experienced yourself), which is why the module system tries to do away with it. It prefers having all dependencies in one folder to check all of them at once. This (a) means it applies more checks (e.g. for duplicates; making the configuration more consistent and thus more reliable) and (b) that compiler and JVM are more likely to see the same configuration (making it more reproducible and thus more reliable). – Nicolai Parlog Dec 07 '17 at 08:29
  • @Nicolai Nonsense. Classpath wildcard expansion and the modulepath equivalent (including all modules in a directory) are nothing more than convenience features. The JDK authors rightfully opted to throw an error in the case of ambiguity for modulepath rather than fail silently as the classpath mechanism does, but this in no way implies them trying to do away with explicit ordering. Resource and class shadowing are extremely legitimate use-cases and can be implemented reliably by listing the individual module entries. In case you didn't read below, this is a confirmed bug in Maven. – Gili Dec 07 '17 at 10:09
  • Yes, "do away with" is not correct - "put less emphasis on" would have been better. And yes, shadowing is a legitimate use case. But it is an uncommon one with a brittle solution. It is hard to argue the opposite considering [your reason for relying on it](https://stackoverflow.com/q/45823641/2525313) and the fact that it uncovered a bug in the most popular Java build tool. Also, [Robert Scholte seems to share my opinion on ordering](https://issues.apache.org/jira/browse/MCOMPILER-317?focusedCommentId=16280827). – Nicolai Parlog Dec 07 '17 at 10:39
  • Look, I'm not saying you, the JPMS, or Maven are doing anything wrong - my argument is just that most use cases and the goal of reliable configuration would be better served if dependencies were routinely exposed to all of the module system's ambiguity checks. – Nicolai Parlog Dec 07 '17 at 10:39