5

I have a JavaFX Tetris clone that I made originally using an ide and maven for dependency management. Everything works great - however, I want to know how everything is working under the hood, so I am trying to compile and run the app in my ubuntu terminal without using an ide or maven.

I have all my javafx dependencies and sqlite-jdbc driver/slj4 jars in my project structure.

Here are the steps I am taking to attempt to compile and run the app:

  1. cd into project root directory, ~/Tetris, which contains the directories, lib, bin, and src. Lib contains javafx modules, bin is for .class file output and project resources (image files, sound files, database for top scores), and src contains source code.

  2. compile the program:

    javac --module-path lib --add-modules javafx.base,javafx.controls,javafx.graphics,javafx.media -d bin src/main/java/com/example/tetris/*.java

Everything works fine up to this point.

  1. Run the program:

    java --module-path lib --add-modules javafx.base,javafx.controls,javafx.graphics,javafx.media -cp bin:lib/sqlite-jdbc-3.45.3.0.jar:lib/sl4j-api-1.7.36.jar src/main/java/com/example/tetris/Main.java

In this command I add sqlite driver and sl4j to the classpath and attempt to run the program. At first, I get console output signifying that the program is running: Table 'top_players' created successfully. Number of rows in the table: 3 N: AAA, S: 10000 N: BBB, S: 5000

But shortly after, it crashes and I get this cryptic error:

Exception in Application start method
Exception in thread "main" java.lang.IllegalArgumentException: 0 > -4
    at java.base/java.util.Arrays.copyOfRange(Arrays.java:3782)
    at java.base/java.util.Arrays.copyOfRange(Arrays.java:3742)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:431)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
    at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)

That is all it gives me, no other information. Does anyone have any idea what I could be missing or doing wrong?

If it helps, I am on an Ubuntu system. I have jdk and javafx added to my PATH. I am genuinely stumped. Thanks for anyone that sees and responds.

1
  • Did you want to run a .java file directly? (probably not, because then you wouldn't need the compile step in your question). That is supported by JEP 330, but it is limited in what it can do compared to running precompiled code. I advise compiling the code first (with javac, via a build tool like Maven or Gradle), then running the compiled code. Instructions for that are at openjfx.io. Commented May 6, 2024 at 19:33

1 Answer 1

5

That error is (what I consider) a bug1 in the implementation of JEP 330: Launch Single-File Source-Code Programs (and the JEP 458: Launch Multi-File Source-Code Programs enhancement). It can be reproduced with the following:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        throw new RuntimeException("test");
    }

    public static void main(String[] args) {
        launch(Main.class, args);
    }
}

Command line:

java -p <path-to-javafx> --add-modules javafx.graphics Main.java

Output:

Exception in Application start method
Exception in thread "main" java.lang.IllegalArgumentException: 0 > -2
        at java.base/java.util.Arrays.copyOfRange(Arrays.java:3807)
        at java.base/java.util.Arrays.copyOfRange(Arrays.java:3767)
        at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.execute(SourceLauncher.java:273)
        at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.run(SourceLauncher.java:153)
        at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.main(SourceLauncher.java:78)

The source of this crytpic error seems to be the handling of InvocationTargetException (link to Java 22 source code) by JEP 330/458. My understanding of that code is that it is trying to hide the SourceLauncher methods from the stack trace when main throws an exception. For the most part this seems to work just fine. But it fails if the exception thrown out of main has a stack trace of less than approximately 5 elements. And from what I can gather, this is the case above because the exception thrown out of launch was created in a different thread than the one that called main, and that other thread does not have that deep of a call stack at the time.

Regardless, you are compiling your program first, so you should not be using the JEP 330 feature to begin with. Which means you should be passing a class name to java, not a source file. Based on the information you provided in your question, your command should be:

java --module-path lib \
     --add-modules javafx.controls,javafx.media \
     --class-path bin:lib/sqlite-jdbc-3.45.3.0.jar:lib/sl4j-api-1.7.36.jar \
     com.example.tetris.Main

Note a few things:

  1. The last bit is the fully qualified name of the main class (instead of a path to a source file). This class will be located on the class-path.

  2. Only javafx.controls and javafx.media need to be included in --add-modules, because the other required JavaFX modules will be implicitly pulled in by those two requiring them. Though adding each individual JavaFX module explicitly is not wrong, just unnecessary.

  3. You can use -cp instead of --class-path and -p instead of --module-path.

That said, your application is still likely to throw an exception. But now you should be able to see the real exception instead of that cryptic IllegalArgumentException.


You mention in your title you are running a "modular application". If what you mean by that is your own code is modular (i.e., you have a module-info.java file), then your command should actually be:

java --module-path lib:bin --module app/com.example.tetris.Main

Note: You can use -p instead of --module-path and -m instead of --module.

Assuming your module descriptor looks something like:

module app {
    requires java.sql;
    requires javafx.controls;
    requires javafx.media;

    exports com.example.tetris to
        javafx.graphics;
}

Note: Include requires org.slf4j if your own code uses it. Otherwise, the sqlite-jdbc driver requires it already. Similarly, you can requires org.xerial.sqlitejdbc if you use it directly, otherwise you should let it be found via the service-provider mechanism (the java.sql module uses a service that the org.xerial.sqlitejdbc module provides).


1. I submitted a bug for this once, but it was rejected as "not reproducible". The comment explained what they did to try to reproduce it and they failed to use JEP 330. My follow up received no response. So, as far as I know, the OpenJDK developers remain unaware of this issue.

Sign up to request clarification or add additional context in comments.

5 Comments

Wow thank you for the detailed response kind stranger, this is awesome. Apologies for perhaps using the term modular incorrectly - i was using a module-info.java file that was generated by intellij when i initially made the game in the ide, but I wanted to learn how to run the program just using the terminal, so I am not using a module-info.java file in this version - by modular, i just meant that it is a multi-class program that is launched by 1 class that extends Application.
The reason I explicitly put the path the sl4j in the classpath is because i was getting an error saying that sqlite-jdbc driver reauired it and it couldnt be found.
UPDATE: I have it working now. Thanks to the help with that error message, I was able to track down the cause of the real problem. Thanks so much for taking the time to help me. Have a good day.
"The reason I explicitly put the path the sl4j in the classpath is because i was getting an error saying that sqlite-jdbc driver reauired it and it couldnt be found" -- When launching your code from the class-path that is the correct thing to do. The notes I have about requiring org.slf4j only apply if your code has a module-info descriptor and you launch your application from the module-path.
"UPDATE: I have it working now." -> what exactly did you do to make it work?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.