Hot questions for Using RxJava 2 in gradle

Question:

While creating a hello world program I got this exception. Here is the code:

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

/**
 * Created by veneet on 30/04/17.
 */
public class MainApp {
    public static void main(String[] args) {
        // This is where the exception occurs.
        Observable<String> observable = Observable.create(e -> {
            e.onNext("Hello World!");
            e.onNext("Hello World!");
            e.onNext("Hello World!");
            e.onNext("Hello World!");
            e.onNext("Hello World!");
            e.onNext("Hello World!");

            e.onComplete();
        });
        Observer<String> observer = new Observer<String>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(String s) {
                System.out.println(s);
            }

            @Override
            public void onError(Throwable e) {
                System.err.println(e.getMessage());
            }

            @Override
            public void onComplete() {

            }
        };
        observable.subscribeOn(Schedulers.io());
        observable.subscribe(observer);

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The build.gradle dependencies are like this:

dependencies {
    compile "io.reactivex.rxjava2:rxjava:2.1.0"
    // https://mvnrepository.com/artifact/org.reactivestreams/reactive-streams
    compile group: 'org.reactivestreams', name: 'reactive-streams', version: '1.0.0.final'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

The complete stacktrace is like this(I think the first line is totally unrelated, but putting it to ensure):

objc[3423]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/bin/java (0x10b6dc4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10d0194e0). One of the two will be used. Which one is undefined.
Exception in thread "main" java.lang.NoClassDefFoundError: org/reactivestreams/Publisher
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at MainApp.main(MainApp.java:11)
Caused by: java.lang.ClassNotFoundException: org.reactivestreams.Publisher
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 13 more

Answer:

From the comments:

you don't have to include a dependency on Reactive-Streams because RxJava has a compile-time dependency on it already. Otherwise, the right version is:

compile 'org.reactivestreams:reactive-streams:1.0.0'

and for the Test Compatibility Kit:

testCompile 'org.reactivestreams:reactive-streams-tck:1.0.0'

The .final was a release mistake I guess.

Question:

I am running some app with JavaFX 11 (OpenFX). The app is written in IntellijIdea and built with gradle. When running in IDE everything's ok. When building a jar it builds successfully but when I try to execute it I get an error:

Exception in thread "main" java.lang.NoClassDefFoundError: io/reactivex/subjects/Subject

To build a jar I use onslip.gradle-one-jar plugin. My gradle is the following:

plugins {
   id 'application'
   id 'org.openjfx.javafxplugin' version '0.0.5'
   id 'com.github.onslip.gradle-one-jar' version '1.0.5'
}

repositories {
   mavenCentral()
}

dependencies {
   implementation "org.openjfx:javafx-base:11:win"
   implementation "org.openjfx:javafx-graphics:11:win"
   implementation "org.openjfx:javafx-controls:11:win"
   implementation "org.openjfx:javafx-fxml:11:win"
   implementation 'com.jfoenix:jfoenix:9.0.8' 
   implementation group: 'commons-validator', name: 'commons-validator', version: '1.6' 
   implementation group: 'commons-io', name: 'commons-io', version: '2.6' 
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59' 
   implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1' 
   implementation group: 'com.sun.mail', name: 'javax.mail', version: '1.6.2' 
   implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.5' 
   testImplementation group: 'junit', name: 'junit', version: '4.12'
}

javafx {
   modules = [ 'javafx.controls', 'javafx.fxml' ]
}

mainClassName = 'jetliner.Main'

jar {
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    manifest {
        attributes 'Main-Class': 'jetliner.Main'
    }
}

task awesomeFunJar(type: OneJar) {
    mainClass = 'jetliner.Main'
}

For jar building I run awesomeFunJar task.


Answer:

The main problem you have in your build is related to how you define the dependencies with implementation (not compile anymore), while on the other hand, you build your jar based on configurations.compile.

Since implementation and compile are not the same, configurations.compile contains only the classes of your project, not the third party dependencies (including JavaFX).

Running the build task will generate a very small fat jar of 3 KB. Obviously this jar misses all the classes from the dependencies.

Solution

Replace in your jar task configurations.compile with configurations.compileClasspath:

jar {
    from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    manifest {
        attributes 'Main-Class': 'jetliner.Main'
    }
}

Run ./gradlew build to generate a fat jar (around 23 MB with the given dependencies), that can be run with:

java -jar build/libs/myproject.jar

Or run ./gradlew awesomeFunJar to produce a similar fat jar (21 MB), that runs with:

java -jar build/libs/myproject-standalone.jar

In both cases, the io.reactivex.rxjava2 dependencies are included.

Note 1: I haven't used this gradle-one-jar plugin before, and I don't see any major advantage in having the custom loader, all I see is that for a helloFX sample takes really too long to load it compare with the regular fat jar.

If you use it because the fat jar doesn't run, maybe you need a Launcher class, as explained here:

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

and then replace the mainClass with jetliner.Launcher.

Note 2: The JavaFX plugin (latest version 0.0.7) uses implementation, and it takes care of adding the JavaFX dependencies, so you can simplify the build file:

plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.7'
    id 'com.github.onslip.gradle-one-jar' version '1.0.5'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'com.jfoenix:jfoenix:9.0.8'
    implementation group: 'commons-validator', name: 'commons-validator', version: '1.6'
    implementation group: 'commons-io', name: 'commons-io', version: '2.6'
    implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'
    implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
    implementation group: 'com.sun.mail', name: 'javax.mail', version: '1.6.2'
    implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.5'
    testImplementation group: 'junit', name: 'junit', version: '4.12'
}

javafx {
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

mainClassName = 'jetliner.Launcher'

jar {
    manifest {
        attributes 'Main-Class': 'jetliner.Launcher'
    }
    from {
        configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

task awesomeFunJar(type: OneJar) {
    mainClass = 'jetliner.Main'
}