Package conflicts with automatic modules in Java 9
Asked Answered
C

3

31

With Java 9 on the close horizon I thought it would be a good learning exercise to port some of my projects over to Java 9. In one of my projects I have dependencies for rxjava and rxjavafx

dependencies {
    compile 'io.reactivex:rxjava:1.2.6'
    compile 'io.reactivex:rxjavafx:1.0.0'
    ...
}

I want to create this project as a named-module. To do this I need to create a module-info.java file and I need to specify the requirements for rxjava and rxjavafx here. However, these libs don't have any module info yet.

In order to work around this I've read that I need to create Automatic Modules. From what I understand, I need to rename the rxjava and rxjavafx jars to have a simple name and then list the jars in the --module-path parameter. I then add a requires directive in my module-info.java with the jar names.

module com.foo.bar {
    requires rxjavafx;
    requires rxjava;
}

I wrote a gradle task to edit the jar names for me, and it appears to be working in most cases. It takes all the jars that need to be compiled and renames them to not include version-info or slashes. The files are then concatenated into a : separated string:

tasks.withType(JavaCompile) {
    delete { delete '/tmp/gradle' }
    copy {
        from configurations.compile + configurations.testCompile
        into '/tmp/gradle'
        rename '(.*)-[0-9]+\\..*.jar', '$1.jar'
        rename { String fileName -> fileName.replace("-", "") }
    }
    options.compilerArgs += ['--module-path', fileTree(dir: '/tmp/gradle', include: '*.jar').getFiles().join(':')]
}

Naturally the rx libraries share some of their package names... this however causes the compiler to spit back errors such as:

error: module  reads package rx.subscriptions from both rxjava and rxjavafx
error: module  reads package rx.schedulers from both rxjava and rxjavafx
error: module  reads package rx.observables from both rxjava and rxjavafx
error: module rxjava reads package rx.subscriptions from both rxjavafx and rxjava
error: module rxjava reads package rx.schedulers from both rxjavafx and rxjava
error: module rxjava reads package rx.observables from both rxjavafx and rxjava
error: module rxjavafx reads package rx.subscriptions from both rxjava and rxjavafx
error: module rxjavafx reads package rx.schedulers from both rxjava and rxjavafx
error: module rxjavafx reads package rx.observables from both rxjava and rxjavafx

It seems like the only way to get around this issue would be to re-package the contents of rxjava and rxjavafx into a single jar and add that as a single module. This doesn't seem like a good solution though...

So my questions are:

  • Am I using the new module system correctly?
  • What can I do about this error? and
  • Do these dependencies prevent me from updating, or should I just wait for rx to update their libs?

Note: I've tried running this with standard java/javac and they cause the same issues. Also here is my java version:

java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+140)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+140, mixed mode)
Coomer answered 21/2, 2017 at 3:26 Comment(2)
Small improvements (possibly): When extracting the module name from the JAR name, the module system will throw out substrings that look like versions ~> try it without renaming them. The module path accepts directories as well ~> in case you have a dir that only contains dependencies, put it on the module path directly.Muscarine
Unrelated to your current question, I'd suggest upgrading to a newer build of JDK 9. Build 148 (I think) made some significant changes to the accessibility of private types and members via reflection. Current build is 157.Aryl
C
29

Am I using the new module system correctly?

Yes. What you are seeing is intended behavior, and this is because JPMS modules do not allow split packages.

In case you are not familiar with the term "split packages" it essentially means two members of the same package coming from two different modules.

For example:
com.foo.A (from moduleA.jar)
com.foo.B (from moduleB.jar)

What can I do about this error?

You have two options:

  1. (harder) "unsplit" the package dependencies. However this could be difficult or impossible if you are not familiar with the inner workings of the library
  2. (easier) combine the two jars into a single jar (and therefore a single automatic module) as you mentioned above. I agree that it is not a "good" solution, but having split packages in the first place is generally not a good idea either.

Do these dependencies prevent me from updating, or should I just wait for rx to update their libs?

Hopefully rx will eventually update their libs to not have split packages at some point in the future. Until then, my recommendation would be to just smash the two jars together into a single jar (option #2).

Coalition answered 21/2, 2017 at 3:38 Comment(9)
Hmm that's a bit disappointing to hear. I guess it wouldn't be too hard to write a gradle task for combining the split-packages. Gonna have to be clever about it though. Don't want to destroy my build time re-packaging someone else's libs ¯\_(ツ)_/¯Coomer
Yea, it's a pain but afaik there are security+performance benefits to disallowing split packages. Hopefully lib owners will be quick about providing combined jars. Or I could see maven central providing auto-combined jars.Coalition
It's funny to read java 9 migration notes that jdk9 will very unlikely break your code. But this restriction breaks tons of libraries. Split packages is a very common practice for libraries that are distributed via several jars.Deglutinate
@Deglutinate It is important to note there are 2 distinct phases to JDK 9 migration -- 1) "tolerating" java 9 (i.e. running applications as unnamed modules) and then 2) "adoption" of java 9 (i.e. converting applications to use module-info's so they are named modules). I believe Phase 1 will be very unlikely to break people's code, but Phase 2 no doubt is going to be a big headache.Coalition
This is sad. Basically, it means that I cannot use jsr305 annotations because they are located in javax.annotation package which already exists in java.base.Whipping
You are probably using a very old JDK build. The jsr305 annotations have been in their own module for a while now which is not included in the java.se aggregate module. So this module is not added to the JDK by default.Coalition
can you explain me how I can smash two jars into a single ? For example when I have this error: [ERROR] module validation.api reads package javax.annotation from both tomcat.embed.core and java.xml.ws.annotationDenadenae
you can unzip two jars into the same dir and then zip them back up into a single archive (and rename file extension from .zip to .jar)Coalition
@AndyGuibert well... what if I need two jars via requires in my module-info.java, but they both contain the same package name? It seems that this is what happens to us at the moment. I understand that simply renaming should work, but we get those jars from a third party which makes this a bit nasty. Or just may be I a am missing something?Prevail
S
4

I had a similar problem:

error: module flyway.core reads package javax.transaction.xa from both jboss.transaction.api.1.2.spec and java.sql
error: module slf4j.api reads package javax.transaction.xa from both jboss.transaction.api.1.2.spec and java.sql
error: module hibernate.core reads package javax.transaction.xa from both jboss.transaction.api.1.2.spec and java.sql
.../src/main/java/module-info.java:1: error: module eu.com.x reads package javax.transaction.xa from both java.sql and jboss.transaction.api.1.2.spec

I could get rid of split packages compilation problem by checking my project transitive dependencies ("gradle dependencies" or "mvn dependency:tree" could be helpful) and excluding by code similar to:

configurations.all {
    exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.2_spec'
}

or

<dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.2.10.Final</version>
      <exclusions>
        <exclusion>
          <groupId>org.jboss.spec.javax.transaction</groupId>
          <artifactId>jboss-transaction-api_1.2_spec</artifactId>
        </exclusion>
      </exclusions> 
    </dependency>
  </dependencies>

No jar repackaging was needed im my problem. This problem have not occurred on #JDK8. Probably excluding dependencies does not help in every project.

Specular answered 13/8, 2017 at 12:38 Comment(1)
I have this exact same problem, but in this case excluding the boss-transaction-api_1.2_spec dependency failed to work. It contains additional classes in it's java.transaction.xa package that are not in the same package exported by the java.sql module. Consequently I get java.lang.ClassNotFoundException runtime exceptions instead. I might have to 'unmodularise' my project until this is resolved.Bouldon
M
1

i have been facing the same issue with java.transaction.xa read package from both javaee and java.transaction.xa. And i fixed it by adding this line to my modul-info.java

 opens javax.transaction.xa;

It worked fine but a hint shown up saying the package javax.transaction.xa is empty or doesn't exists. however the source code compile correctly.

Macilroy answered 30/10, 2018 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.