2

I am trying to access javafx.web/com.sun.webkit.dom from my unnamed module javafx project. For this, I have created a class com.sun.webkit.dom.DomMapper. I do not see any error in the IDE, but when I run it using mvn clean javafx:run it complains -

Exception in thread "JavaFX Application Thread" java.lang.NoClassDefFoundError: com/sun/webkit/dom/DomMapper
....
Caused by: java.lang.ClassNotFoundException: com.sun.webkit.dom.DomMapper
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
...

This is how my pom.xml looks

<?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> ... </groupId>
    <artifactId> ... </artifactId>
    <version> ... </version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <exec.mainClass> .... </exec.mainClass>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>11</version>
        </dependency> 
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-graphics</artifactId>
            <version>11</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-web</artifactId>
            <version>11</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.14.3</version>
        </dependency> 
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.4</version>
                <configuration>
                    <options>
                        <option>--add-exports</option><option>javafx.web/com.sun.webkit.dom=ALL-UNAMED</option>
                        <option>--add-opens</option><option>javafx.web/com.sun.webkit.dom=ALL-UNAMED</option>
                    </options>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <compilerArgs>
                        <arg>--add-exports</arg><arg>javafx.web/com.sun.webkit.dom=ALL-UNAMED</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

If I use the Maven shade plugin, I am able to bypass this issue. I think it has got to do with how JPMS security works, but I cannot figure out how to bypass this. I tried add-exports, and add-opens in various combinations, but it did not help me. What am I doing wrong?

BTW, I want to map the WebKit DOM to the JSoup dom element, as they give a nice API for CSS selectors.

Update I am adding an additional stack trace of the error

java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.NoClassDefFoundError: com/sun/webkit/dom/DomMapper
    at xyz.jphil.internal_browser.TestApp.start(TestApp.java:86)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    ... 1 more
Caused by: java.lang.ClassNotFoundException: com.sun.webkit.dom.DomMapper
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 10 more
  • You created `com.sun.webkit.dom.DomMapper`? As in, you wrote that class yourself? If so, I would put it in your own package, not `com.sun.webkit.dom` (if your code was modular, you'd be getting a split package error; not actually sure you should not be getting that error anyway). Anyway, if your code is on the class-path (i.e., the unnamed module), then using `--add-exports javafx.web/com.sun.webkit.dom=ALL-UNNAMED` should work for you. – Slaw May 13 '22 at 07:51
  • yes i wrote it. Adding `--add-exports javafx.web/com.sun.webkit.dom=ALL-UNNAMED` is not working. I have to put in the `com.sun.webkit.dom` so that it can access the package private functions. – Ivan Velikanov May 13 '22 at 08:31
  • 1
    Please edit this question to include the full stack trace. That should tell you what is trying to access the class. If the accessing class is not an unnamed module, then opening the package to `ALL_UNNAMED` is not going to work, instead you need to name the module. For instance if you are accessing from your app and your app is modular, then you need provide your module's name. If the access is via jsoup, the module name for jsoup is `org.jsoup` as seen in the `MANIFEST.MF` file in its jar: `Automatic-Module-Name: org.jsoup`. – jewelsea May 13 '22 at 18:20
  • 1
    If you want further assistance, I suggest you provide a [mcve] so that your issue can be replicated via copy and paste. – jewelsea May 13 '22 at 18:22
  • 1
    "I have to put in the com.sun.webkit.dom so that it can access the package private functions." -> that may not work for a modular app due to Java Platform Module rules. I don't know for sure, as I am not an expert in that, I think it could run into issues such as [split packages](https://stackoverflow.com/questions/51828014/how-split-packages-are-avoided-in-java-9). Perhaps such issues can be avoided if your app is not modular (I am not sure). – jewelsea May 13 '22 at 18:25
  • "If I use maven shade plugin, I am able to bypass this issue." -> likely because a shaded jar does not use the module system, it places everything on the classpath (which is an unsupported configuration for executing code in JavaFX modules, but usually currently works with JDK/JavaFX 17). – jewelsea May 13 '22 at 18:31
  • @jewelsea thanks for the valuable tips. I have added the stack trace. I will try out the suggestions and see if it works, I am managing for the time being using Maven shade, in the long term I would need to make it without shade because debugging is difficult and the build process is longer. I will also post a minimal reproducible example if I am unable to solve this problem even after using the inputs given. Finally, I will update here on what finally worked. – Ivan Velikanov May 14 '22 at 00:13
  • Note that `--add-exports` will solve an `IllegalAccessError`. It won't solve a `ClassNotFoundException`, as that's a different kind of failure. I wonder, does the CNFE go away if you put the class in your own package instead of `com.sun.webkit.dom` (ignoring the need for package-private access for now)? – Slaw May 14 '22 at 00:16
  • Yes, ClassNotFoundException is not faced with the code in my own package. – Ivan Velikanov May 14 '22 at 00:20
  • 1
    Interesting (and I can confirm). My guess: This is a problem with how the class loader locates classes. It tries to load `com.sun.webkit.dom.DomMapper` by first looking at the package and finds that the `com.sun.webkit.dom` package is in the `javafx.web` module. But the `DomMapper` class is not in that module. Since Java modules do not allow split-packages, it decides the class cannot possibly exist. That's why a `ClassNotFoundException` is thrown. I don't know of a workaround right now (perhaps something to do with `--patch-module`? Though I've never used that before). – Slaw May 14 '22 at 00:29
  • The reason the problem goes away when you create a fat/uber JAR with the Maven shade plugin was explained by [@jewelsea 's comment](https://stackoverflow.com/questions/72226094/javafx-access-internal-webkit-document?noredirect=1#comment127621755_72226094). – Slaw May 14 '22 at 00:32
  • @Slaw user read about `--patch-module` this seems to be the way around this. Or as @jewelsea answered/suggested in another related question here https://gist.github.com/jewelsea/6722397 , it seems it is safer/better to manage entirely with public API only. – Ivan Velikanov May 14 '22 at 01:47

1 Answers1

2

The solution for this problem lies in using using --patch-module. As I have added a class - com.sun.webkit.dom.DomMapper which is completely sealed by JPMS. The reason why I added DomMapper in the com.sun.webkit.dom.DomMapper was to access some package-private functions.

However, I see that can be easily avoided so I am choosing to avoid using those package-private functions. With this --patch-module is not required, which is also not a recommended thing to do as per JPMS documentation. Now the solution is simpler. Adding --add exports is enough (which is already there in the POM) and I am able to get it working.

Finally exporting the entire program as a farjar using maven shade plugin for example, also works. This I am adding just for information.

This answer is based on comments and valuable inputs by Jewelsea and Slaw. Further notes:

  • This example shows the public (although internal) functions of the com.sun.webkit.dom are quite good as it is, gist.github.com/jewelsea/6722397
  • The problem I created by making a class inside com.sun.webkit.dom is called split-package - read here How split packages are avoided in Java 9