nats-io / nats.java

Java client for NATS
Apache License 2.0
568 stars 154 forks source link

GraalVM Native SocketDataPort ClassNotFoundException again #1021

Closed Alfablos closed 12 months ago

Alfablos commented 1 year ago

Observed behavior

Hello, I'm trying to build a small program as a binary using GraalVM Native. It compiles fine but when I run the executable the following exception is thrown:

Exception in thread "main" java.lang.IllegalArgumentException: java.lang.ClassNotFoundException: io.nats.client.impl.SocketDataPort
        at io.nats.client.Options$Builder.createInstanceOf(Options.java:875)
        at io.nats.client.Options.buildDataPort(Options.java:1733)
        at io.nats.client.impl.NatsConnection.tryToConnect(NatsConnection.java:424)
        at io.nats.client.impl.NatsConnection.connect(NatsConnection.java:207)
        at io.nats.client.impl.NatsImpl.createConnection(NatsImpl.java:29)
        at io.nats.client.Nats.createConnection(Nats.java:303)
        at io.nats.client.Nats.connect(Nats.java:155)
        at com.example.hyprtesseract.StorageInterface.<init>(StorageInterface.java:24)
        at com.example.hyprtesseract.HyprTesseract.main(HyprTesseract.java:24)
        at java.base@21/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.ClassNotFoundException: io.nats.client.impl.SocketDataPort
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86)
        at java.base@21/java.lang.Class.forName(DynamicHub.java:1346)
        at java.base@21/java.lang.Class.forName(DynamicHub.java:1309)
        at java.base@21/java.lang.Class.forName(DynamicHub.java:1302)
        at io.nats.client.Options$Builder.createInstanceOf(Options.java:871)
        ... 9 more
Oct 21, 2023 6:58:46 PM io.nats.client.impl.ErrorListenerLoggerImpl exceptionOccurred
SEVERE: exceptionOccurred, Exception: java.lang.IllegalArgumentException: java.lang.ClassNotFoundException: io.nats.client.impl.SocketDataPort

I've searched for similar issues and the only one I found is this one (116) from micronaut-nacts, Graalvm - Nats - Problems when running the application. It appears that the solution was confirmed here.

This all dates back to 2020. I'm not using any particular framework, just vanilla Java for now and I'm unsure about how to have that class resolved.

Thank you

Expected behavior

Binary executable runs with no exceptions.

Server and client version

nats-server 2.10.3 jnats 2.17.1, 2.16.11 GraalVM (JDK) 17, 21 gradle 8.3

Host environment

EndeavourOS (Arch)

build.gradle.kts

java.sourceCompatibility = JavaVersion.VERSION_21

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

graalvmNative {
    testSupport.set(false)
    binaries {
        named("main") {
            buildArgs.add("-H:+StaticExecutableWithDynamicLibC")
            javaLauncher.set(javaToolchains.launcherFor {
                languageVersion.set(JavaLanguageVersion.of(21))
                vendor.set(JvmVendorSpec.matching("GraalVM Community"))
            })

            debug.set(true)
        }
        all {
            resources.autodetect()
        }
    }
    toolchainDetection.set(true)
}

Steps to reproduce

Create an instance of "Connection" by connecting to a server and then run the task nativeRun in gradle (or nativeCompile and then run the executable).

scottf commented 12 months ago

We are aware that nats is not GraalVM friendly but as you said it's pretty plain vanilla, so it can't to too far, but needs a full analysis.

scottf commented 12 months ago

@Alfablos Can you try this snapshot: 2.17.1.GraalVM-remove-reflection-SNAPSHOT

Not sure how you get your dependencies. For gradle and maven, you can get instructions here: https://github.com/nats-io/nats.java#using-gradle

If you need, I could get you the jar directly. There is also a branch in the repo: https://github.com/nats-io/nats.java/tree/GraalVM-remove-reflection

scottf commented 12 months ago

Also, there may be issues compiling directly with Java 17

Alfablos commented 12 months ago

Thank you! So, the error about the SocketDataPort is gone.

I have an exception related to Jackson trying to deserialize the message payload but I'm not sure this is something that needs to be addressed your side. Basically Payload is a record defined as follows:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record Payload(String name, String bucket, String nuid, String mtime, long size, int chunks, String digest) {

    public int chunks() {
        return this.chunks;
    }
}

Deserialization fails with this error:

SEVERE: exceptionOccurred, Connection: 28, Exception: java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.hyprtesseract.Payload`: cannot deserialize from Object value (no delegate- or property-based Creator): this appears to be a native image, in which case you may need to configure reflection for the class that is to be deserialized

I haven't tried using a conventional class though.

I always get this error regardless of the usage of GraalVM: Oct 23, 2023 10:45:09 PM io.nats.client.impl.ErrorListenerLoggerImpl exceptionOccurred But it doesn't impact functionality.

Java 17 + GraalVM: same jackson error with the SNAPSHOT version, ClassNotFoundException (SocketDataPort) with the stable one. Java 17 no GraalVM: everything works. Java 21 no GraalVM: everything works.

:)

scottf commented 12 months ago

The library does not use jackson so that is on your side. I saw that the GraalVM docs have specific notes about serialization, I would start there.

As far as the ErrorListenerLoggerImpl exceptionOccurred is there any more context around this?

Alfablos commented 12 months ago

The library does not use jackson so that is on your side. I saw that the GraalVM docs have specific notes about serialization, I would start there. I will, thanks!

As far as the ErrorListenerLoggerImpl exceptionOccurred is there any more context around this? That particular error went away once I installed a proper logging facility (Slf4j + Logback). Another error shows up when I download a file from a bucket:

Oct 23, 2023 11:34:37 PM io.nats.client.impl.ErrorListenerLoggerImpl flowControlProcessed
INFO: flowControlProcessed, Connection: 245, Subscription: 2098327494, Consumer Name: mqFMjLUafg, FlowControlSource:FLOW_CONTROL

But again, this is most likely on me. This little repo is a draft for testing some functionalities and I wasn't that careful about configuration. I mentioned It in case It was "clearly" something wrong :)

Should I expect the fix for the ClassNotFoundException coming to a stable release anytime soon?

Thank you very much!

scottf commented 12 months ago

Yeah, I'll merge that in and will be available in a couple weeks. As soon as I merge it will be available in the 2.17.1-SNAPSHOT

scottf commented 12 months ago

@Alfablos Merged https://github.com/nats-io/nats.java/pull/1022 Will be available in the next release.

Alfablos commented 12 months ago

Thank you :)