2

I am developing a javaFX 11 application that uses the javafx.scene.image class to load images from a URL:

Image​(String url, boolean backgroundLoading)

where backgroundLoading is set to true.

The application works fine when run from my Eclipse IDE (using Maven). But when the application is built as a modular (JRT) application and I run the launcher to test the build, my Image objects do not load from their assigned URLs (https protocol) and instead indicate an error. The exception returned is:

java.util.concurrent.ExecutionException: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

I have tried switching backgroundLoading to false and tried URLs from other sites that provide access to images. I have even tried changing the protocol property of the URLs from "https" to "http". Still the same outcome.

I have an ImageHandler class for handling the construction and setting of javafx.scene.image objects and another class that uses the ImageHandler to set the image of a javafx.scene.image.ImageView object (by using a listener to check when the background loading has finished).

I cannot provide the full code to reproduce the problem, but here are some snippets from the aforementioned classes:

ImageHandler - getImage():

public Image getImage() {
    if (this.image == null || this.imageHadErrorLoading()) {
        this.imageUrl = String.format(Settings.GATHERER_URL + ImageHandler.QUERY_DATA, this.multiverseId);
        LoggerUtil.logger(this).log(Level.INFO, String.format("URL for image: %s", this.imageUrl));
        try {
            this.image = new Image(this.imageUrl, this.backgroundLoading);
            this.setImageError(false);  
        } catch (Exception e) {
            LoggerUtil.logger(this).log(Level.SEVERE, String.format("Exception creating new Image: %s", e.toString()));
        }
    }

    return this.image;
}

ViewController - setCurrentImage():

private void setCurrentImage(int multiverseId) {
    ImageHandler imageHandler;
    imageHandler = new ImageHandler(multiverseId, true);
    Image cardImage = imageHandler.getImage();

    // If the image was previously loaded (successfully), just set the image.
    // Otherwise, use a listener to monitor the loading of the image which
    // eventually sets the image once it has successfully loaded.
    if (imageHandler.imageHasLoaded()) {
        this.cardImageView.setImage(cardImage);
        LoggerUtil.logger(this).log(Level.INFO, String.format("Multiverse ID %d: Image cached....getting image....", multiverseId));
    } else {
        // This listener on the image's progress is used to set the Image View when the image has finally loaded. In the meantime,
        // the Image View will continue to display the "placeholder" image.
        cardImage.progressProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                double cardProgress = (double) newValue;
                if (cardProgress == 1.0) {
                    if (cardImage.isError()) {
                        cardImageView.setImage(imageHandler.getErrorImage());
                        LoggerUtil.logger(this).log(Level.SEVERE, String.format("Multiverse ID %d: Error loading image.", multiverseId));
                        LoggerUtil.logger(this).log(Level.SEVERE, String.format("Image exception was: %s", cardImage.getException().getMessage()));
                        LoggerUtil.logger(this).log(Level.SEVERE, String.format("Image exception was: %s", cardImage.getException()));
                    } else {
                        cardImageView.setImage(cardImage);
                        imageHandler.setImageLoaded(true);
                        LoggerUtil.logger(this).log(Level.INFO, String.format("Multiverse ID %d: Image loaded successfully! Image URL: %s", multiverseId, cardImage.getUrl()));
                    }
                }
            }
        });
    }

Expected outcome: Application displays images when run from a built JRT module and has the same behaviour as if run from the Eclipse IDE.

Actual outcome: Application images produce SSLHandshakeException when run from a built JRT module

user207421
  • 305,947
  • 44
  • 307
  • 483
Cladgeman
  • 113
  • 1
  • 6
  • What modules are included in your generated runtime? – swpalmer Sep 04 '19 at 01:20
  • Hi @swpalmer. The following modules are included: - javafx.controls - javafx.fxml - javafx.graphics - javafx.base - java.logging - javafx.web - com.fasterxml.jackson.core - com.fasterxml.jackson.databind - com.fasterxml.jackson.annotation - json.simple (NB: I had to modify the *fasterxml* and *json.simple* jars (downloaded by Maven) to turn them into modular jars.) – Cladgeman Sep 04 '19 at 13:13
  • 1
    I cannot, based on your description, reproduce the problem with OpenJDK 11.0.2/OpenJFX 11.0.2 or OpenJDK 12.0.2/OpenJFX 12.0.2 on Windows 10. Please create and provide a [mre] demonstrating the problem. It'd probably be helpful to provide the URLs of the images you're trying to display, as well. – Slaw Sep 04 '19 at 14:04
  • @Slaw That's probably the next step, to provide a reproducible example. I was hoping initially that someone might recognize the symptoms because it's happened to them! As far as the URLs, I've tried different sites, which produce the same outcome. – Cladgeman Sep 04 '19 at 18:27
  • Try running google searches on `handshake_failure`, perhaps some of the info you get from that might help you to troubleshoot your issue. It is possible that the server you are communicating is configured to auto-redirect to only serve https, which might be why be why you still get ssl exceptions when you are using http. The SSL crypto suites and protocols and certificate chain on the server may not match something your Java client is configured to understand. You should supply the actual url of the server serving the image so somebody helping could test it. – jewelsea Sep 04 '19 at 18:43
  • Create a basic one or two line program and see if you can read the input stream from a URL (independent of JavaFX and everything else), then [troubleshoot](https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html) from there. Be aware that the weeds are very high for this kind of stuff, so if you go deep in them, prepare to get lost ;-) – jewelsea Sep 04 '19 at 18:47
  • Thanks for the ideas, @jewelsea. I will look into that. I suppose it still puzzles me why my application works, regarding the image URLs, when running from my Eclipse IDE, yet does not work when run from the built JRT image! – Cladgeman Sep 04 '19 at 18:57
  • 2
    Likely the built JRT image is missing the required crypto libs, certificate authority validation certificates or crypto policy settings or the server is misconfigured. Nobody can really help you with that at the moment as there is no information in your question on how you built the JRT image and to what service you are trying to connect, so nobody could build the same image and connect to the same service to try to debug it. That you are using JavaFX, Images, background loading etc, is irrelevant to your issue. – jewelsea Sep 04 '19 at 21:22
  • 2
    Perhaps this is a duplicate of: [SSLHandshakeException with jlink created runtime](https://stackoverflow.com/questions/55439599/sslhandshakeexception-with-jlink-created-runtime) or [Received fatal alert: handshake_failure in jlinked JRE](https://stackoverflow.com/questions/54770538/received-fatal-alert-handshake-failure-in-jlinked-jre) – jewelsea Sep 04 '19 at 21:26
  • 1
    Thanks for those links @jewelsea. That's spot on! The problem was that my compiled JRT image needed *jdk.crypto.ec* as a module option. It's used by the JDK when running the application from Eclipse, which is why it worked in that case, but must be specifically added as a module option when building a JRT image. – Cladgeman Sep 04 '19 at 22:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/198992/discussion-between-jewelsea-and-cladgeman). – jewelsea Sep 05 '19 at 08:13

1 Answers1

2

Thanks to @jewelsea I found out that I needed to add jdk.crypto.ec as a module option when building the application using the javafx-maven-plugin. See this answer for the details.

It worked OK when using OpenJDK 11 in Eclipse and running from source, but certain modules such as jdk.crypto.ec must get omitted by default when building a JRT image through jlink, unless specifically added.

Here is my updated javafx-maven-plugin definition in my Maven POM:

<plugin>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-maven-plugin</artifactId>
    <version>0.0.2</version>
    <configuration>
        <release>11</release>
        <jlinkImageName>thecollector</jlinkImageName>
        <launcher>launcher</launcher>
        <mainClass>thecollector/com.cladge.thecollector.MainApp</mainClass>
        <options>
            <option>--add-modules</option>
            <option>jdk.crypto.ec</option>
        </options>
    </configuration>
</plugin>
Cladgeman
  • 113
  • 1
  • 6
  • 2
    I would guess that reason it got omitted is that it isn't an explicit dependency of the modules that you are directly linked with. The crypto stuff uses the service provider mechanism so crypto providers can be discovered at runtime rather than be explicitly linked. – swpalmer Sep 06 '19 at 01:52