Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Boot 3.1 cannot use Elasticsearch Client 8.9.0 #36669

Closed
fabian-froehlich opened this issue Aug 2, 2023 · 8 comments
Closed

Spring Boot 3.1 cannot use Elasticsearch Client 8.9.0 #36669

fabian-froehlich opened this issue Aug 2, 2023 · 8 comments
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@fabian-froehlich
Copy link

fabian-froehlich commented Aug 2, 2023

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 elastic/elasticsearch-java#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 spring-projects/spring-data-elasticsearch@412a2e2. 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 elastic/elasticsearch-java#642, not sure which project needs to act)

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 2, 2023
@philwebb
Copy link
Member

philwebb commented Aug 2, 2023

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 philwebb added the status: waiting-for-feedback We need additional information before we can continue label Aug 2, 2023
@philwebb philwebb changed the title Elasticsearch Client 8.9.0 breaks with SpringBoot 3.1 Spring Boot 3.1 cannot use Elasticsearch Client 8.9.0 Aug 2, 2023
@philwebb
Copy link
Member

philwebb commented Aug 2, 2023

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.

@fabian-froehlich
Copy link
Author

fabian-froehlich commented Aug 2, 2023

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.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 2, 2023
@fabian-froehlich
Copy link
Author

fabian-froehlich commented Aug 2, 2023

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.

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.

@wilkinsona
Copy link
Member

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.

@fabian-froehlich
Copy link
Author

fabian-froehlich commented Aug 2, 2023

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.

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();
    }

@wilkinsona
Copy link
Member

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.

@wilkinsona wilkinsona closed this as not planned Won't fix, can't repro, duplicate, stale Aug 10, 2023
@wilkinsona wilkinsona added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged status: feedback-provided Feedback has been provided for: team-meeting An issue we'd like to discuss as a team to make progress labels Aug 10, 2023
@patpatpat123
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

5 participants