apache / apache/maven

[MNG-7855] Dependencies wrongly put on class-path rather than module-path

Open
#9,065 2 comments 1 reaction 0 assignees View on GitHub
bug priority:major
Dominant language
Java
Stars
5.3k
Forks
3.1k
Avg merge
20h 42m
Merged PRs (30d)
297

Description

**[Martin Desruisseaux](https://issues.apache.org/jira/secure/ViewProfile.jspa?name=desruisseaux)** opened **[MNG-7855](https://issues.apache.org/jira/browse/MNG-7855?redirect=false)** and commented

When invoking Java tools such as `java` or `javac`, the project dependencies can be declared either in a `\--class-path` or `\--module-path` option. Maven choose automatically the module-path if all the following conditions are true:

1. the dependency is modularized (i.e. contains a `module-info.class` file or an `Automatic-Module-Name` attribute in `MANIFEST.MF`), and
2. the project using the dependency is itself modularized.

Condition #1 is fine, but #2 is problematic. The fact that a dependency is declared on the class-path rather than the module-path changes the way that `java.util.ServiceLoader` discovers the provided services.

* If the dependency is on the class-path, `ServiceLoader` scans the content of `META-INF/services` directory.
* If the dependency is on the module-path, `ServiceLoader` uses the declarations in `module-info.class`.

Even if condition #2 is false (i.e. a project is not modularized), modularized dependencies still need to be declared on the module-path _for allowing the dependency to discover its own services, or the services of a transitive modularized dependency_. If a modularized dependency is put on the class-path instead, it has consequence not only for the project using that dependency, **but also for the dependency itself, which become unable to use its own `module-info.class`**.

# Demonstration

The attached test case contains two Maven modules, named `service` and `client`. The first Maven module declares a dummy services with 4 providers, named A, B, C and D. Providers A and D are declared in `module-info`. Providers B and C are declared in `META-INF/services`. A `ShowMyServices` class lists the services discovered by `java.util.ServiceLoader`.

The second Maven module has only a main method invoking `ShowMyServices`. This second module intentionally has no `module-info.java` file. The use case is a big module that we cannot modularize immediately (because modularization brings stronger encapsulation, which requires significant changes in the project to modularize), but still want to use modularized dependencies. The test case can be run with `mvn install`. During test execution, the following is printed:

```
Running test.client.MainTest
Start searching for services...
Provider B declared in META-INF.
Provider C declared in META-INF.
Done.
The dependency has been loaded as an unnamed module.
Consequently its `module-info` file has been ignored,
and the `META-INF/services` directory is used instead.
```

The above test demonstrates that `module-info` has been ignored in the context of JUnit test execution. The same behaviour happens also with `mvn exec:java` executed in the `client` sub-directory.

## Expected behaviour

The Maven behaviour can be reproduced on the command-line as below (Linux convention). This command put everything on the class-path:

```bash
java --class-path service/target/service-1.0.jar:client/target/client-1.0.jar test.client.Main
```

The expected behaviour can be reproduced with the following command-line. This command put the modularized dependency on the module-path while keeping the non-modularized client on the class-path:

```bash
java --module-path service/target/service-1.0.jar --class-path client/target/client-1.0.jar --add-modules ALL-MODULE-PATH test.client.Main
```

The latter command produces the following output:

```
Start searching for services...
Provider A declared in module-info.
Provider D declared in module-info.
Done.
The dependency has been loaded as named module. Great!
This is what we need for the `module-info` to be used.
```

# Discussion

Unless Maven provides configuration options that we did not see, the way that Maven decides what to put on `\--class-path` and what to put on `\--module-path` is a blocker issue for gradual modularisation of large projects. This is because Maven choices break usages of `java.util.ServiceLoader` in the dependencies themselves, which developers may not control. The workaround for library developers is to declare all service providers in both `module-info` and `META-INF/services`, with the risk of inconsistencies. This workaround forces developers to renounce to the usage of `provider()` static methods (which was making possible to use singleton provider instances), because `provider()` static method works only for providers declared in `module-info`. If the library developers didn't applied such workaround, then the library users are blocked if they are not in capacity to modularize their own project immediately (unless those users are experts capable to create workarounds themselves).

Ideally, developers should have explicit control on whether to put a dependency on the class-path or module-path. There are scenarios where a developer way want to force Maven to put a dependency on the module-path even for a non-modularized module, for example if the developer really wants automatic module. Conversely, forcing a modularized dependency to be on the class-path may be useful for testing purposes, for example for replacing the service providers declared in that module by patched services declared in `META-INF/services` elsewhere (it does not need to be in the patched module).

## Proposal

Keep current behavior unchanged as the default behavior. Add somewhere an option for declaring how to handle a dependency. I suggest to do that in the `` block. For example:

```xml

foo
bar
true

```

More JPMS-related options could be added in the future. For example an `` block meaning "when using this dependency, add `\--add-exports` statement from this project to that dependency for the packages listed inside this `` block. That would be useful for JUnit testing among other. But this is not the purpose of the JIRA issue.

**References:**

* [Test case on GitHub](https://github.com/Geomatys/MavenModulepathBug)
* [Post on user mailing list](https://lists.apache.org/thread/2kn195rrpxnw2t2bvxk4drzoo0c04959)

---

**Affects:** 3.8.6, 4.0.0-alpha-7

**Attachments:**
- [MavenModulepathBug.zip](https://issues.apache.org/jira/secure/attachment/13061698/MavenModulepathBug.zip) (_8.32 kB_)

**Issue Links:**
- [SUREFIRE-2097](https://issues.apache.org/jira/browse/SUREFIRE-2097) More configuration for surefire JPMS

- [MNG-8015](https://issues.apache.org/jira/browse/MNG-8015) Control the type of path where each dependency can be placed
(_**"depends upon"**_)

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.