How to make Spring boot webflux to start on default Netty rather than Jetty while testing

Viewed 1947

I have a somewhat similar gradle and spring webflux config up as described in Why spring webflux is choosing jetty by default and then failing?

But our goal is to have in memory Amazon dynamo db + spring webflux on netty (and NOT jetty) for our unit testing ( we already have production dynamo db with spring webfux on netty ). I am able to run unit tests using in-memory dynamo db but as soon as I enable springboot webflux, it starts complaining :

   Caused by: java.lang.NoClassDefFoundError: org/eclipse/jetty/servlet/ServletHolder
    at org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory.createJettyServer(JettyReactiveWebServerFactory.java:176) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory.getWebServer(JettyReactiveWebServerFactory.java:106) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext$ServerManager.<init>(ReactiveWebServerApplicationContext.java:202) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext$ServerManager.get(ReactiveWebServerApplicationContext.java:221) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.createWebServer(ReactiveWebServerApplicationContext.java:90) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.onRefresh(ReactiveWebServerApplicationContext.java:79) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
    ... 62 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.servlet.ServletHolder

I have tried following in my build.gradle :

 configurations {
    // exclude Reactor Jetty /Tomcat 
    compile.exclude  group: 'org.springframework.boot',module: 'spring-boot-starter-jetty'
    testcompile.exclude  group: 'org.springframework.boot',module: 'spring-boot-starter-jetty'
    //compile.exclude group: 'javax.servlet' , module: 'servlet-api' //<--tried servlet jar exclusion also
}

.....
testCompile ('com.amazonaws:DynamoDBLocal:1.11.477')
....//event tried this ( and is probably wrong)
testCompile("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
        exclude  group: 'org.springframework.boot', module: 'spring-boot-starter-jetty' //by both name and group
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'

    }

I checked dependency graph :

\--- com.amazonaws:DynamoDBLocal:1.11.477
    +--- org.antlr:antlr4-runtime:4.7.2
    +--- commons-cli:commons-cli:1.2
    +--- org.apache.commons:commons-lang3:3.8.1
    +--- com.almworks.sqlite4java:libsqlite4java-linux-i386:1.0.392
    |    \--- com.almworks.sqlite4java:sqlite4java:1.0.392
    +--- com.almworks.sqlite4java:libsqlite4java-linux-amd64:1.0.392
    |    \--- com.almworks.sqlite4java:sqlite4java:1.0.392
    +--- com.almworks.sqlite4java:sqlite4java-win32-x64:1.0.392
    |    \--- com.almworks.sqlite4java:sqlite4java:1.0.392
    +--- com.almworks.sqlite4java:sqlite4java-win32-x86:1.0.392
    |    \--- com.almworks.sqlite4java:sqlite4java:1.0.392
    +--- com.almworks.sqlite4java:libsqlite4java-osx:1.0.392
    |    \--- com.almworks.sqlite4java:sqlite4java:1.0.392
    +--- com.amazonaws:aws-java-sdk-core:1.11.477 (*)
    +--- com.amazonaws:aws-java-sdk-dynamodb:1.11.477 (*)
    +--- org.apache.logging.log4j:log4j-api:2.6.2 -> 2.11.2
    +--- org.apache.logging.log4j:log4j-core:2.6.2 -> 2.11.2
    |    \--- org.apache.logging.log4j:log4j-api:2.11.2
      +--- org.eclipse.jetty:jetty-client:8.1.12.v20130726 -> 9.4.18.v20190429
    |    +--- org.eclipse.jetty:jetty-http:9.4.18.v20190429
    |    |    +--- org.eclipse.jetty:jetty-util:9.4.18.v20190429
    |    |    \--- org.eclipse.jetty:jetty-io:9.4.18.v20190429
    |    |         \--- org.eclipse.jetty:jetty-util:9.4.18.v20190429
    |    \--- org.eclipse.jetty:jetty-io:9.4.18.v20190429 (*)
    +--- org.eclipse.jetty:jetty-server:8.1.12.v20130726 -> 9.4.18.v20190429
    |    +--- javax.servlet:javax.servlet-api:3.1.0 -> 4.0.1
    |    +--- org.eclipse.jetty:jetty-http:9.4.18.v20190429 (*)
    |    \--- org.eclipse.jetty:jetty-io:9.4.18.v20190429 (*)
    +--- org.mockito:mockito-core:1.10.19 -> 2.23.4
    |    +--- net.bytebuddy:byte-buddy:1.9.3 -> 1.9.12

So what I need is to force webflux to have Netty as server. I think it is getting overridden by dynamodb dependency or some jar transitivity.

3 Answers

I ran into this exact same issue. The problem is that the DynamoDBLocal library transitively references Jetty. With both Jetty and Netty on the classpath, Webflux thinks it should start up using Jetty instead of Netty. I was able to solve this by following what is detailed in the Spring Boot docs here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-web-servers.html#howto-configure-webserver

Specifically this part:

As a last resort, you can also declare your own WebServerFactory component, which will override the one provided by Spring Boot.

More concretely, you can add this to your test configuration (or your entire app configuration if you want to specifically tell everything to always use Netty):

@Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
    return new NettyReactiveWebServerFactory();
}

Same issue here. I suspect that some internal condition forces the Tomcat instead of Netty, but I couldn't figure out.

I solved the issue combining this three changes:

  1. Added org.springframework.boot:spring-boot-starter-reactory-netty dependecy
  2. Added a NettyReactiveWebServerFactory bean (Thanks to Evan Lennick!)
  3. Added spring.main.web-appplication-type=reactive to application.properties

I hope this could help!

Related