Search code examples
spring-bootgroovyspring-data-elasticsearch

Spring Boot with ElasticSearch in Groovy: WebClient not present Exception


I am running a small demo spring-boot application where I want to connect my url routes with elasticsearch queries. The app starts up just fine with those gradle dependencies:

implementation('org.springframework.data:spring-data-elasticsearch')
implementation('org.elasticsearch:elasticsearch')
implementation('org.elasticsearch.client:elasticsearch-rest-high-level-client')
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.codehaus.groovy:groovy')

As soon as I add the class ElasticsearchClientConfig (not yet used by me, just loaded):

package me.spring.GroovyDemo.store

import me.spring.GroovyDemo.AppConstants
import org.elasticsearch.client.RestHighLevelClient
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.data.elasticsearch.client.ClientConfiguration
import org.springframework.data.elasticsearch.client.RestClients
import org.springframework.data.elasticsearch.core.ElasticsearchOperations
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories

@Configuration
@EnableElasticsearchRepositories(basePackages = "me.spring.GroovyDemo")
@ComponentScan(basePackages = ["me.spring.GroovyDemo"])
class ElasticsearchClientConfig {
    @Bean
    RestHighLevelClient client() {
        ClientConfiguration clientConfiguration
                = ClientConfiguration.builder()
                .connectedTo(AppConstants.ELASTIC_SERVER)
                .build()

        return RestClients.create(clientConfiguration).rest()
    }

    @Bean
    ElasticsearchOperations elasticsearchTemplate() {
        return new ElasticsearchRestTemplate(client())
    }
}

The app fails on startup. I found many similar issues. But none of them seems to fail with missing Webclient. I don't really get that I would need a WebClient in the first place. The exception is as follows:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.0)

2021-05-27 12:02:32.598  INFO 4462 --- [           main] m.s.GroovyDemo.GroovyDemoApplication     : Starting GroovyDemoApplication using Java 1.8.0_292 on vagrant-VirtualBox with PID 4462 (/home/vagrant/GroovyOpenApi/out/production/classes started by vagrant in /home/vagrant/GroovyOpenApi)
2021-05-27 12:02:32.600  INFO 4462 --- [           main] m.s.GroovyDemo.GroovyDemoApplication     : No active profile set, falling back to default profiles: default
2021-05-27 12:02:32.890  WARN 4462 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder    : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
2021-05-27 12:02:33.230  INFO 4462 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Elasticsearch repositories in DEFAULT mode.
2021-05-27 12:02:33.240  INFO 4462 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 Elasticsearch repository interfaces.
2021-05-27 12:02:33.402  INFO 4462 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Elasticsearch repositories in DEFAULT mode.
2021-05-27 12:02:33.404  INFO 4462 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 1 ms. Found 0 Elasticsearch repository interfaces.
2021-05-27 12:02:33.407  INFO 4462 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Reactive Elasticsearch repositories in DEFAULT mode.
2021-05-27 12:02:33.409  INFO 4462 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 1 ms. Found 0 Reactive Elasticsearch repository interfaces.
2021-05-27 12:02:33.799  INFO 4462 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-05-27 12:02:33.807  INFO 4462 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-05-27 12:02:33.807  INFO 4462 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.46]
2021-05-27 12:02:33.861  INFO 4462 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-05-27 12:02:33.861  INFO 4462 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1065 ms
2021-05-27 12:02:34.035  WARN 4462 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'client' defined in class path resource [me/spring/GroovyDemo/store/ElasticsearchClientConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.RestHighLevelClient]: Factory method 'client' threw exception; nested exception is java.lang.TypeNotPresentException: Type org.springframework.web.reactive.function.client.WebClient not present
2021-05-27 12:02:34.038  INFO 4462 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2021-05-27 12:02:34.054  INFO 4462 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-05-27 12:02:34.078 ERROR 4462 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'client' defined in class path resource [me/spring/GroovyDemo/store/ElasticsearchClientConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.RestHighLevelClient]: Factory method 'client' threw exception; nested exception is java.lang.TypeNotPresentException: Type org.springframework.web.reactive.function.client.WebClient not present
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.7.jar:5.3.7]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.7.jar:5.3.7]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) [spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:438) [spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:337) [spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1336) [spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1325) [spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication$run.call(Unknown Source) [spring-boot-2.5.0.jar:2.5.0]
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47) [groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) [groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148) [groovy-3.0.8.jar:3.0.8]
    at me.spring.GroovyDemo.GroovyDemoApplication.main(GroovyDemoApplication.groovy:10) [classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.RestHighLevelClient]: Factory method 'client' threw exception; nested exception is java.lang.TypeNotPresentException: Type org.springframework.web.reactive.function.client.WebClient not present
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.7.jar:5.3.7]
    ... 23 common frames omitted
Caused by: java.lang.TypeNotPresentException: Type org.springframework.web.reactive.function.client.WebClient not present
    at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117) ~[na:1.8.0_292]
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125) ~[na:1.8.0_292]
    at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) ~[na:1.8.0_292]
    at sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68) ~[na:1.8.0_292]
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138) ~[na:1.8.0_292]
    at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) ~[na:1.8.0_292]
    at sun.reflect.generics.repository.MethodRepository.getReturnType(MethodRepository.java:68) ~[na:1.8.0_292]
    at java.lang.reflect.Method.getGenericReturnType(Method.java:255) ~[na:1.8.0_292]
    at java.beans.FeatureDescriptor.getReturnType(FeatureDescriptor.java:370) ~[na:1.8.0_292]
    at java.beans.Introspector.getTargetEventInfo(Introspector.java:1052) ~[na:1.8.0_292]
    at java.beans.Introspector.getBeanInfo(Introspector.java:427) ~[na:1.8.0_292]
    at java.beans.Introspector.getBeanInfo(Introspector.java:173) ~[na:1.8.0_292]
    at groovy.lang.MetaClassImpl.lambda$addProperties$4(MetaClassImpl.java:3460) ~[groovy-3.0.8.jar:3.0.8]
    at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_292]
    at groovy.lang.MetaClassImpl.addProperties(MetaClassImpl.java:3460) ~[groovy-3.0.8.jar:3.0.8]
    at groovy.lang.MetaClassImpl.reinitialize(MetaClassImpl.java:3442) ~[groovy-3.0.8.jar:3.0.8]
    at groovy.lang.MetaClassImpl.initialize(MetaClassImpl.java:3435) ~[groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:273) ~[groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:315) ~[groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:258) ~[groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:987) ~[groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallStaticSite(CallSiteArray.java:71) [groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:156) [groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47) [groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) [groovy-3.0.8.jar:3.0.8]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:130) [groovy-3.0.8.jar:3.0.8]
    at me.spring.GroovyDemo.store.ElasticsearchClientConfig.client(ElasticsearchClientConfig.groovy:21) ~[classes/:na]
    at me.spring.GroovyDemo.store.ElasticsearchClientConfig$$EnhancerBySpringCGLIB$$a214165c.CGLIB$client$0(<generated>) ~[classes/:na]
    at me.spring.GroovyDemo.store.ElasticsearchClientConfig$$EnhancerBySpringCGLIB$$a214165c$$FastClassBySpringCGLIB$$c29866d5.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.7.jar:5.3.7]
    at me.spring.GroovyDemo.store.ElasticsearchClientConfig$$EnhancerBySpringCGLIB$$a214165c.client(<generated>) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_292]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_292]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_292]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_292]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.7.jar:5.3.7]
    ... 24 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.web.reactive.function.client.WebClient
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_292]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_292]
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) ~[na:1.8.0_292]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_292]
    at java.lang.Class.forName0(Native Method) ~[na:1.8.0_292]
    at java.lang.Class.forName(Class.java:348) ~[na:1.8.0_292]
    at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114) ~[na:1.8.0_292]
    ... 60 common frames omitted


Process finished with exit code 1

Somehow I am not able to create the ES client bean. My App is in package me.spring.GroovyDemo, everything else is below. I tried adding some dependencies, like webflux to get WebClient, but did not really help.

What is the reason for the failing startup?


Solution

  • I got your example running by adding

    implementation('org.springframework:spring-webflux')
    

    Where does this reference to the WebClient class come from? Spring Data Elasticsearch is built with an optional dependency to org.springframework:spring-webflux because it contains code for both the imperative and the reactive setup. Optional because we need it to build the library, but you do not need it when running - in imperative mode.

    Now the ClientConfiguration class and its builder have a method withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer). This function is compiled into the library.

    When running an imperative, non-reactive application which does not have webflux in it's classpath in Java or Kotlin everything is fine - the application itself was never compiled to use this function and so it is not needed and the transitive optional dependency is never resolved. It does not matter if these optional dependencies are in the loaded class as long as they are not used - otherwise the compiler would have complained.

    In Groovy this seems to be different. Here there is some Java-bean inspection running on the ClientConfiguration class which finds the afore mentioned method but cannot find the WebClient class, because that's not on the classpath.

    So by adding the dependency to the classpath this error goes away by the cost of having a library with reactive code in the classpath - now some Spring Boot austoconfiguration could be tempted to pick something up from there.

    I don't know what would be a better solution, or if groovy can handle optional dependencies better, I'm not using groovy. Someone with more insights might have a better answer.