Confluence plugin can't load reactivestreams API for Spring WebFlux WebClient

I’m writing a plugin for Confluence and trying to use Spring WebFlux’s WebClient but the plugin will not load into Confluence because of classloader problems. I’m using Spring Java Config if that makes any difference.

In my pom.xml I have:

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webflux</artifactId>
      <version>5.3.32</version>
    </dependency>
    <dependency>
      <groupId>io.projectreactor.netty</groupId>
      <artifactId>reactor-netty-http</artifactId>
      <version>1.1.18</version>
    </dependency>
    <dependency>
      <groupId>io.projectreactor.netty</groupId>
      <artifactId>reactor-netty-core</artifactId>
      <version>1.1.18</version>
    </dependency>
    <dependency>
      <groupId>org.reactivestreams</groupId>
      <artifactId>reactive-streams</artifactId>
      <version>1.0.4</version>
    </dependency>

These JARs get included into my plugin when built, but when Confluence loads the plugin I get the errors:

[INFO] [talledLocalContainer] 15:30:30,854 ERROR [ThreadPoolAsyncTaskExecutor::Thread 11] [osgi.factory.OsgiPlugin] onPluginContainerFailed Unable to start the plugin container for plugin 'com.myapp'
[INFO] [talledLocalContainer] org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'webClient' defined in com.myapp.SpringConfig: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.reactive.function.client.WebClient]: Factory method 'webClient' threw exception; nested exception is java.lang.NoClassDefFoundError: org/reactivestreams/Publisher
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:649)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:477)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955)
[INFO] [talledLocalContainer]   at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:929)
[INFO] [talledLocalContainer]   at org.eclipse.gemini.blueprint.context.support.AbstractDelegatedExecutionApplicationContext.access$1600(AbstractDelegatedExecutionApplicationContext.java:57)
[INFO] [talledLocalContainer]   at org.eclipse.gemini.blueprint.context.support.AbstractDelegatedExecutionApplicationContext$4.run(AbstractDelegatedExecutionApplicationContext.java:322)
[INFO] [talledLocalContainer]   at org.eclipse.gemini.blueprint.util.internal.PrivilegedUtils.executeWithCustomTCCL(PrivilegedUtils.java:85)
[INFO] [talledLocalContainer]   at org.eclipse.gemini.blueprint.context.support.AbstractDelegatedExecutionApplicationContext.completeRefresh(AbstractDelegatedExecutionApplicationContext.java:287)
[INFO] [talledLocalContainer]   at org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor$CompleteRefreshTask.run(DependencyWaiterApplicationContextExecutor.java:137)
[INFO] [talledLocalContainer]   at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[INFO] [talledLocalContainer]   at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
[INFO] [talledLocalContainer]   at java.base/java.lang.Thread.run(Thread.java:1583)
[INFO] [talledLocalContainer] Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.reactive.function.client.WebClient]: Factory method 'webClient' threw exception; nested exception is java.lang.NoClassDefFoundError: org/reactivestreams/Publisher
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:644)
[INFO] [talledLocalContainer]   ... 19 more
[INFO] [talledLocalContainer] Caused by: java.lang.NoClassDefFoundError: org/reactivestreams/Publisher
[INFO] [talledLocalContainer]   at org.springframework.web.reactive.function.client.DefaultWebClientBuilder.initConnector(DefaultWebClientBuilder.java:289)
[INFO] [talledLocalContainer]   at org.springframework.web.reactive.function.client.DefaultWebClientBuilder.build(DefaultWebClientBuilder.java:266)
[INFO] [talledLocalContainer]   at org.springframework.web.reactive.function.client.WebClient.create(WebClient.java:154)
[INFO] [talledLocalContainer]   at com.myapp.SpringConfig.webClient(SpringConfig.java:45)
[INFO] [talledLocalContainer]   at com.myapp.SpringConfig$$EnhancerBySpringCGLIB$$e0c54048.CGLIB$webClient$5(<generated>)
[INFO] [talledLocalContainer]   at com.myapp.SpringConfig$$EnhancerBySpringCGLIB$$e0c54048$$FastClassBySpringCGLIB$$7475d418.invoke(<generated>)
[INFO] [talledLocalContainer]   at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
[INFO] [talledLocalContainer]   at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
[INFO] [talledLocalContainer]   at com.myapp.SpringConfig$$EnhancerBySpringCGLIB$$e0c54048.webClient(<generated>)
[INFO] [talledLocalContainer]   at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
[INFO] [talledLocalContainer]   at java.base/java.lang.reflect.Method.invoke(Method.java:580)
[INFO] [talledLocalContainer]   at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
[INFO] [talledLocalContainer]   ... 20 more
[INFO] [talledLocalContainer] Caused by: java.lang.ClassNotFoundException: org.reactivestreams.Publisher not found by com.atlassian.platform.dependencies.platform-spring-bundle [7]
[INFO] [talledLocalContainer]   at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1591)
[INFO] [talledLocalContainer]   at org.apache.felix.framework.BundleWiringImpl.access$300(BundleWiringImpl.java:79)
[INFO] [talledLocalContainer]   at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1976)
[INFO] [talledLocalContainer]   at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
[INFO] [talledLocalContainer]   ... 32 more

The line org.reactivestreams.Publisher not found by com.atlassian.platform.dependencies.platform-spring-bundle [7] suggests OSGi is trying to load the reactive-streams dependency from one of the built-in bundles, but (a) it doesn’t exist and (b) I’m trying to import it from my own bundle.

If I add org.reactivestreams to the <Import-Package> element, I get an error that it can’t be found. The JAR is definitely bundled into my plugin. Why can’t it be found?

Your <Import-Package> instructions are interesting, but the acid test is to inspect target/classes/META-INF/MANIFEST.MF to see whether your bundle is actually trying to import the org.reactivestreams package (it should not be of course, because you’re bundling it).

It’s a long shot, but have you tried setting <extractDependencies> to false in your AMPS configuration, just in case the extraction of your bundled JARs is breaking them somehow? This is more likely with artifacts that contain text files that are important at runtime, for example META-INF/spring.schemas. It doesn’t affect Java classes so much because they are usually disambiguated by their package names.

Hi, thanks for looking!

MANIFEST.MF doesn’t contain org.reactivestreams, as expected. Changing <extractDependencies> from true to false makes no difference either.

From the OSGi browser I can see that the bundle mentioned in the stack trace, com.atlassian.platform.dependencies.platform-spring-bundle [7], also optionally imports org.reactivestreams but the OSGi browser doesn’t show that it’s provided by anything. So as another long shot I tried exporting that package…and that made no difference.

<rant>At this point I’m giving up on developing the plugin. I’m an experienced Java developer but I’ve spent three weeks trying to get a simple plugin to compile and run, and can’t even do that. The documentation is woeful and out of date where it does exist, the SDK isn’t updated any more, error messages don’t make any sense… Oh well.</rant>

Sorry you’re having trouble with your plugin.

I can’t promise to solve your problem, but if you could provide a minimal project that demonstrates the issue, that would give people a great starting point.