Problem description
There seems to be a breaking change in the API of the Elasticsearch Java Client in 8.9.0 which was introduced in https://github.com/elastic/elasticsearch-java/pull/584.
Boot's inner class ElasticsearchTransportConfiguration inside ElasticsearchClientConfigurations tries to creates an RestClientTransport by passing TransportOptions. But due to the change it now expects a RestClientOptions.
There is already work done in spring-data-elasticsearch https://github.com/spring-projects/spring-data-elasticsearch/commit/412a2e2ea1a57889b80bd1da7d1f377984e92448. But we do not use spring-data-elasticsearch in our project.
We use
spring-boot-starter-parent in 3.1.2
together with
spring-retry
spring-boot-starter-cache
spring-boot-starter-security
spring-boot-starter-oauth2-resource-server
spring-boot-starter-data-redis
spring-boot-starter-aop
together with
co.elastic.clients.elasticsearch-java - the new one, not the High-Level-Rest-Client
Is it possible to implement a workaround on my side? Is it possible to see a fix in the 3.1.X train?
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientConfigurations$ElasticsearchTransportConfiguration.restClientTransport(ElasticsearchClientConfigurations.java:91)
The following method did not exist:
'void co.elastic.clients.transport.rest_client.RestClientTransport.<init>(org.elasticsearch.client.RestClient, co.elastic.clients.json.JsonpMapper, co.elastic.clients.transport.TransportOptions)'
The calling method's class, org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientConfigurations$ElasticsearchTransportConfiguration, was loaded from the following location:
jar:file:/C:/Users/User/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/3.1.2/spring-boot-autoconfigure-3.1.2.jar!/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchClientConfigurations$ElasticsearchTransportConfiguration.class
The called method's class, co.elastic.clients.transport.rest_client.RestClientTransport, is available from the following locations:
jar:file:/C:/Users/User/.m2/repository/co/elastic/clients/elasticsearch-java/8.9.0/elasticsearch-java-8.9.0.jar!/co/elastic/clients/transport/rest_client/RestClientTransport.class
The called method's class hierarchy was loaded from the following locations:
co.elastic.clients.transport.rest_client.RestClientTransport: file:/C:/Users/User/.m2/repository/co/elastic/clients/elasticsearch-java/8.9.0/elasticsearch-java-8.9.0.jar
co.elastic.clients.transport.ElasticsearchTransportBase: file:/C:/Users/User/.m2/repository/co/elastic/clients/elasticsearch-java/8.9.0/elasticsearch-java-8.9.0.jar
Action:
Correct the classpath of your application so that it contains compatible versions of the classes org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientConfigurations$ElasticsearchTransportConfiguration and co.elastic.clients.transport.rest_client.RestClientTransport
(Similar to https://github.com/elastic/elasticsearch-java/issues/642, not sure which project needs to act)
Comment From: philwebb
We'll be upgrading to Elasticsearch Java Client 8.9.0 in Spring Boot 3.2 and depending on the fix it might be possible to backport something (perhaps using reflection to invoke the method).
@fabian-froehlich Is there any reason that you need to upgrade the client to 8.9.0? Is staying on the managed 8.7.x version an option for you?
Comment From: philwebb
Is it possible to implement a workaround on my side?
It looks like ElasticsearchTransportConfiguration is @ConditionalOnMissingBean(ElasticsearchTransport.class) so I think you can define your own RestClientTransport bean and have our auto-configuration back off.
Comment From: fabian-froehlich
We'll be upgrading to Elasticsearch Java Client 8.9.0 in Spring Boot 3.2 and depending on the fix it might be possible to backport something (perhaps using reflection to invoke the method).
@fabian-froehlich Is there any reason that you need to upgrade the client to 8.9.0? Is staying on the managed 8.7.x version an option for you?
@philwebb
I think the client version should match the backend version. Our elastic cluster already runs on 8.8.2. We run the official elastic container in our cluster. Updating the elastic-version also provides us with CVE fixes that we try to deploy as soon as possible. to stay on track. Since we are only using the configuration to create the client and we do not depend API features, there was no problem before.
Also the new elasticsearch client was not as mature as expected, when we started the migration. Therefore we are happy for the fixes in newer versions.
Comment From: fabian-froehlich
Is it possible to implement a workaround on my side?
It looks like
ElasticsearchTransportConfigurationis@ConditionalOnMissingBean(ElasticsearchTransport.class)so I think you can define your ownRestClientTransportbean and have our auto-configuration back off.
Could you provide further assistance? My naive approach was to add the following bean to our own configuration
@Bean
@Primary
public RestClientTransport restClientTransport(RestClient restClient, JsonpMapper jsonMapper,
ObjectProvider<RestClientOptions> restClientOptions) {
return new RestClientTransport(restClient, jsonMapper, restClientOptions.getIfAvailable());
}
which results in
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of method restClientTransport in de.dataport.suchdienst.config.ElasticSearchConfiguration required a bean of type 'co.elastic.clients.json.JsonpMapper' that could not be found.
Action:
Consider defining a bean of type 'co.elastic.clients.json.JsonpMapper' in your configuration
There are a few definitions of implementations of this interface in org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientConfigurations that somehow do not get autowired.
Comment From: wilkinsona
The auto-configuration of a JsonpMapper backs off when you define your own RestClientTransport. While it is only the RestClientTransport that uses the mapper, I'm not sure that it should back off like that and we may want to refine this a bit.
In the meantime, this should work:
@Bean
RestClientTransport restClientTransport(RestClient restClient, ObjectProvider<RestClientOptions> restClientOptions) {
return new RestClientTransport(restClient, new JacksonJsonpMapper(), restClientOptions.getIfAvailable());
}
Note that you don't need @Primary as the auto-configuration backing off will mean that there's only one RestClientTransport bean in the context.
Comment From: fabian-froehlich
The auto-configuration of a
JsonpMapperbacks off when you define your ownRestClientTransport. While it is only theRestClientTransportthat uses the mapper, I'm not sure that it should back off like that and we may want to refine this a bit.In the meantime, this should work:
java @Bean RestClientTransport restClientTransport(RestClient restClient, ObjectProvider<RestClientOptions> restClientOptions) { return new RestClientTransport(restClient, new JacksonJsonpMapper(), restClientOptions.getIfAvailable()); }Note that you don't need
@Primaryas the auto-configuration backing off will mean that there's only oneRestClientTransportbean in the context.
Thanks for your help. I highly appreciate that! With that code the application starts again.
I just saw, that we already defined a RestClientTransport object in creation of the ElasticsearchClient Bean. I refactored it with the input from here. Maybe it helps someone that stumbles over this issue:
@Bean
public ElasticsearchClient client(RestClientTransport transport) {
return new ElasticsearchClient(transport);
}
@Bean
public RestClientTransport restClientTransport(
RestClientBuilder restClientBuilder,
ObjectMapper mapper,
SuchdienstProperties properties,
@Qualifier("increasedBuffer") RequestOptions requestOptions
) {
ObjectMapper elasticMapper = mapper.copy();
elasticMapper.registerModule(new JavaTimeModule());
RestClient httpClient = restClientBuilder
.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder.setSocketTimeout(timeout)
.build();
JacksonJsonpMapper jacksonJsonpMapper = new JacksonJsonpMapper(elasticMapper);
RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder();
options.setHttpAsyncResponseConsumerFactory(
new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(
properties.getElastic().getClientBufferSizeByte()
)
);
RestClientOptions restClientOptions = new RestClientOptions(requestOptions);
return new RestClientTransport(httpClient, jacksonJsonpMapper, restClientOptions);
}
@Bean
@Qualifier("increasedBuffer")
public RequestOptions requestOptions() {
RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder();
options.setHttpAsyncResponseConsumerFactory(
new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(
clientBufferSize
)
);
return options.build();
}
Comment From: wilkinsona
We've upgraded to 8.9 in 3.2. Given that there's a workaround (defining your own RestClientTransport) and that #36698 has made that easier, I don't think we should do anything more here.
Comment From: patpatpat123
Hello team,
Just wanted to bump this closed issue (it was opened when I drafted this) and +1 @fabian-froehlich 's post.
We are also using Spring Boot, now 3.1.0-M1, with elasticsearch java (but without spring data elasticsearch)
The PR https://github.com/elastic/elasticsearch-java/pull/584/files (on elasticsearch java side) is actually very interesting for SpringBoot users.
The reason is: SpringBoot users, very very likely have an object, as bean, as reference, of type RestTemplate or WebClient.
The PR from elasticsearch java allows the possibility to take into account the often existing Webclient/RestTemplate, and allow making calls to elasticsearch backend with a client that is "more Spring".
However, indeed, the combination SpringBoot 3.1 + elasticsearch java 8.9 seems to yield many issues.