11

I have edited this to bring things up-to-date wrt the original posting.

I want to try the new Project Loom feature defined in: JEP 428: Structured Concurrency (Incubator)

I have in my pom.xml

<properties>
  <maven.compiler.executable>${env.JAVA_HOME}/bin/javac</maven.compiler.executable>
  <maven.compiler.source>19</maven.compiler.source>
  <maven.compiler.target>19</maven.compiler.target>
</properties>

. . .

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.10.1</version>
  <configuration>
    <compilerArgs>
      <arg>--add-modules=jdk.incubator.concurrent</arg>
      <arg>--enable-preview</arg>
    </compilerArgs>
  </configuration>
</plugin>

where JAVA_HOME points to JDK 19, but when I try to build via mvn compile I get

[ERROR] C:\Users\ERIC\Documents\git\loom-lab\laboratory\src\main\java\net\kolotyluk\loom\Structured.java:3:20:  error: package jdk.incubator.concurrent is not visible
[ERROR] C:\Users\ERIC\Documents\git\loom-lab\laboratory\src\main\java\net\kolotyluk\loom\Structures.java:3:20:  error: package jdk.incubator.concurrent is not visible
. . .

Many people have helped me with this, and clearly they can make it work, but for some reason, I cannot get mvn compile to work.

However, I can get the code to compile and run under IntelliJ. Never before have I been unable to get Maven to compile when I can get IntelliJ to do so. Usually, it is the other way around.

Eric Kolotyluk
  • 1,958
  • 2
  • 21
  • 30
  • 3
    Guess: Perhaps you need a `module-info.java` file containing a `requires` line as discussed [here](https://stackoverflow.com/a/72072054/642706). – Basil Bourque Aug 04 '22 at 01:21
  • 3
    See [JEP 11: Incubator Modules](https://openjdk.org/jeps/11): "_Applications on the class path must use the --add-modules command-line option to request that an incubator module be resolved. Applications developed as modules can specify requires or requires transitive dependences upon an incubator module directly_". So, you need either include an `--add-modules` argument or have a `requires` directive, but not both. Can you get this working on the command line (i.e., with `javac` and `java`)? Perhaps Maven is not doing what you expect. – Slaw Aug 04 '22 at 05:16
  • 2
    What if you change it to `--add-modules=jdk.incubator.concurrent` (with the `=` present) in your POM file? – Slaw Aug 04 '22 at 05:24
  • Note that the module needs to be present at both compile-time and runtime. – dan1st Aug 04 '22 at 08:06

3 Answers3

5

module-info.java file

I do not understand all the moving parts, but I did succeed somehow in accessing the new Project Loom features, virtual threads & structured concurrency, being previewed and incubated respectively in Java 19.

Here is my main method.

record Event( UUID id , Instant when , Integer reading ) {}

try ( var scope = new StructuredTaskScope.ShutdownOnFailure() )
{
    Future < UUID > futureId = scope.fork( ( ) -> UUID.randomUUID() );
    Future < Instant > futureWhen = scope.fork( ( ) -> Instant.now() );
    Future < Integer > futureReading = scope.fork( ( ) -> ThreadLocalRandom.current().nextInt( 1 , 10 ) );

    scope.join();           // Join both forks
    scope.throwIfFailed();  // ... and propagate errors
    Event event = new Event( futureId.get() , futureWhen.get() , futureReading.get() );
    System.out.println( event );
}
catch ( InterruptedException e )
{
    throw new RuntimeException( e );
}
catch ( ExecutionException e )
{
    throw new RuntimeException( e );
}

That code uses these imports:

import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;

When run:

Event[id=de316aca-10b1-41ed-bfc6-732c4e184566, when=2022-08-07T03:04:48.207650Z, reading=9]

I added a module-info.java file outside my package hierarchy, at src/main/java.

module LoomEx {
    requires jdk.incubator.concurrent;
}

And my POM. I started with the Apache Maven QuickStart archetype at listed. Then I updated all the version numbers to the latest.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns = "http://maven.apache.org/POM/4.0.0"
         xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>work.basil.example</groupId>
    <artifactId>Loom</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>Loom</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.release>19</maven.compiler.release>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.0</version>
            <scope>test</scope>
        </dependency>


    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (maybe moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.2.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.3.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.10.1</version>
                    <configuration>
                        <!--<compilerVersion>19</compilerVersion>-->
                        <release>19</release>
                        <!--<compilerArgs>&#45;&#45;source 19</compilerArgs>-->
                        <compilerArgs>--enable-preview</compilerArgs>
                        <!--<compilerArgs>&#45;&#45;add-modules jdk.incubator.concurrent</compilerArgs>-->
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>3.0.0-M7</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>3.0.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>4.0.0-M3</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.4.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

I tried using various Maven POM elements taken from pages such as this and this and this, and hints from JEP 11: Incubator Modules and JEP 428: Structured Concurrency (Incubator), hoping to not need the module-info.java file. But I could not make it work. Adding the module info file was the only route to success for me.

And of course I had to set the usual multiple oddball settings that IntelliJ buries deep within various disparate places to specify use of Java 19. See other Stack Overflow Questions for directions on these troublesome settings. (Drives me batty. Definitely the most annoying problem/flaw in IntelliJ. Why can't IntelliJ just read the Java version from the Maven POM or Gradle settings and be done with it?)

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
4

I managed to get it working with Maven, without needing a module-info.java file. The major difference between my POM and your POM seems to be how I set the compiler arguments. Instead of what you had:

<configuration>
  <compilerArgs>--source 19</compilerArgs>
  <compilerArgs>--enable-preview</compilerArgs>
  <compilerArgs>--add-modules jdk.incubator.concurrent</compilerArgs>
  <compilerVersion>19</compilerVersion>
  <source>19</source>
  <target>19</target>
</configuration>

I have:

<configuration>
  <compilerArgs>
    <arg>--add-modules=jdk.incubator.concurrent</arg>
  </compilerArgs>
</configuration>

Note I have <arg> elements nested in <compilerArgs>. I also have = in the --add-modules argument, though I don't know if that's strictly necessary. And it seems that --enable-preview is only needed at run-time, not compile-time. Other arguments that you have I set a different way (e.g., the <properties> element).


Example

Here is an example, with JAVA_HOME set to a JDK 19 installation.

Source/build files

App.java:

package sample;

import jdk.incubator.concurrent.StructuredTaskScope;

public class App {
  public static void main( String[] args ) throws Exception {
    try (var scope = new StructuredTaskScope<Void>()) {
      scope.fork(() -> delayPrint(1000, "Hello,"));
      scope.fork(() -> delayPrint(2000, "World!"));
      scope.join();
    }
    System.out.println("Done!");
  }

  private static Void delayPrint(long delay, String message) throws Exception {
    Thread.sleep(delay);
    System.out.println(message);
    return null;
  }
}

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>sample</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>sample</name>
  <url>http://maven.apache.org</url>
  
  <properties>
    <maven.compiler.source>19</maven.compiler.source>
    <maven.compiler.target>19</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <build>

    <plugins>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.10.1</version>
        <configuration>
          <compilerArgs>
            <arg>--add-modules=jdk.incubator.concurrent</arg>
          </compilerArgs>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <executable>${java.home}/bin/java</executable>
          <arguments>
            <argument>--add-modules=jdk.incubator.concurrent</argument>
            <argument>--enable-preview</argument>
            <argument>--class-path</argument>
            <classpath/>
            <argument>sample.App</argument>
          </arguments>
        </configuration>
      </plugin>

    </plugins>
  </build>

</project>

Output

Compiling:

> mvn compile

[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------< com.example:sample >-------------------------
[INFO] Building sample 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sample ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\***\Desktop\structured_concurrency\sample\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ sample ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\Users\***\Desktop\structured_concurrency\sample\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.439 s
[INFO] Finished at: 2022-08-08T01:01:26-06:00
[INFO] ------------------------------------------------------------------------

Executing:

> mvn exec:exec

[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------< com.example:sample >-------------------------
[INFO] Building sample 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:3.1.0:exec (default-cli) @ sample ---
WARNING: Using incubator modules: jdk.incubator.concurrent
Hello,
World!
Done!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.610 s
[INFO] Finished at: 2022-08-08T01:02:42-06:00
[INFO] ------------------------------------------------------------------------
Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Thanks, but I cannot get your compilerArgs to work. I always get "error: package jdk.incubator.concurrent is not visible" from "mvn compile" – Eric Kolotyluk Aug 08 '22 at 23:51
  • I copy-pasted into a new project. Oddly, the project compiles and builds. But when running `main`, I get this exception: `Caused by: java.lang.ClassNotFoundException: jdk.incubator.concurrent.StructuredTaskScope`. – Basil Bourque Aug 09 '22 at 00:11
  • @BasilBourque That is strange. The `--add-modules` must be working, or I believe you'd be getting a `FindException`. But to be able to resolve the module and then fail to find a class in said module? I'm not sure what's happening there. I just tried copy-and-pasting into a new project and it still works flawlessly on my computer (Windows 10). – Slaw Aug 09 '22 at 05:41
  • @EricKolotyluk Not sure what that's about. You have `JAVA_HOME` pointing to a JDK 19 installation, right? If it matters, the exact version of Java I'm using is `19-ea+34-2229`. If you're using an older version, perhaps something was fixed since then. I'm also on Windows 10 if that makes a difference, though I'm pretty sure what I've done is platform-independent. – Slaw Aug 09 '22 at 05:43
  • 1
    I could also get this to work on Ubuntu WSL. In both cases (WSL and Windows), I was using Maven 3.8.6 and OpenJDK 19-ea+34-2229. – Slaw Aug 09 '22 at 06:12
  • I can't improve on this answer, but do have a working example, it case it is useful to anyone - https://github.com/codetojoy/gists_java/tree/main/egg_StackOverflow_73229247 . Note the code is quite different, but it illustrates a Maven build. Also, there is a Github Action. (Note the example is a folder in a larger project). – Michael Easter Nov 03 '22 at 03:48
2

A small addition for @BasilBourque excellent answer. For those people that may not use Maven or modules you won't need to make your application a module. The parameters for --enable-preview --add-modules jdk.incubator.concurrent are required in all launchers, whether directly or via Maven.

You should be able to run the concurrency example he has provided from command line with standard classpath and no module-info.java with the following source code launcher or javac/java:

%JAVAHOME%/bin/java --source 19 --enable-preview --add-modules jdk.incubator.concurrent ConcurrencyEx.java

Or compile then run it as:

%JAVAHOME%/bin/javac --source 19 --enable-preview --add-modules jdk.incubator.concurrent -d ../build ConcurrencyEx.java

%JAVAHOME%/bin/java --enable-preview --add-modules jdk.incubator.concurrent -cp ../build ConcurrencyEx

Obviously just substitute %JAVAHOME% with $JAVAHOME to run on Linux instead of Windows.

Source code:

import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import jdk.incubator.concurrent.*;

public class ConcurrencyEx {
    
    // main as from @BasilBourque answer
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        record Event( UUID id , Instant when , Integer reading ) {}

        try ( var scope = new StructuredTaskScope.ShutdownOnFailure() ) {
            Future < UUID > futureId = scope.fork( ( ) -> UUID.randomUUID() );
            Future < Instant > futureWhen = scope.fork( ( ) -> Instant.now() );
            Future < Integer > futureReading = scope.fork( ( ) -> ThreadLocalRandom.current().nextInt( 1 , 10 ) );

            scope.join();           // Join both forks
            scope.throwIfFailed();  // ... and propagate errors
            Event event = new Event( futureId.get() , futureWhen.get() , futureReading.get() );
            System.out.println( event );
        }
    }
}
DuncG
  • 12,137
  • 2
  • 21
  • 33