How to work with Elasticsearch 8 using Spring Boot 3.x and Spring Data Elasticsearch 5.x

H

Hi, Elasticsearch fans

At current article I would like to show you how to work with Elasticsearch 8 version using Spring Boot 3.x version and Spring Data Elasticsearch 5.x package. It appeared that at Spring Data Elasticsearch 5.x version has a lot of different changes. First of all Elasticsearch 8 has security options enabled out of the box. As result the way we have to connect to search engine has changed. At second the way of building queries has changed a lot. I am going to show you according changes via real examples, which really hard to find at internet at that moment. Let’s start from connection. From 5x version the new Elasticsearch client should be used for which we have add ElasticsearchClientConfig class with according properties. Here is the code that will help you to provide connection to the Elasticsearch 8:

//! imports omitted for readability !

@Configuration
public class ElasticsearchClientConfig extends ElasticsearchConfiguration {

    @Value("${spring.elasticsearch.rest.uris}")
    String connectionUrl;

    @Value("${spring.elasticsearch.client.certificate}")
    String certificateBase64;

    @Override
    public ClientConfiguration clientConfiguration() {
        try {
            return ClientConfiguration.builder()
                    .connectedTo(connectionUrl)
                    .usingSsl(getSSLContext())
                    .withBasicAuth("elastic", "test1234")
                    .build();
        } catch (CertificateException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (KeyStoreException e) {
            throw new RuntimeException(e);
        } catch (KeyManagementException e) {
            throw new RuntimeException(e);
        }
    }

    private SSLContext getSSLContext() throws
            CertificateException,
            IOException, NoSuchAlgorithmException,
            KeyStoreException,
            KeyManagementException
    {
        byte[] decode = Base64.getDecoder().decode(certificateBase64);

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        Certificate ca;
        try (InputStream certificateInputStream = new ByteArrayInputStream(decode)) {
            ca = cf.generateCertificate(certificateInputStream);
        }

        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf =
                TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);
        return context;
    }
}

The main complication here is to create proper SSL connection. In that case I am keeping certificated as base64 encoded string at spring application properties. That will allow to build application dynamically for different environments from devops side. The same way you may move authorization credentials, which are hard coded at code above – remember that is only learning example :). So, at getSSLContext method certificated encode string is decoded and then according SSL context is created using CertificateFactory and TrustManagerFactory. Current context is used then by ClientConfiguration.builder. Believe me, it was not easy to make all that stuff working. 🙂

Now let’s speak about queries by itself. Hope that you still remember article “Java Spring Boot ElasticSearch – search service and query builder” from Java Spring tutorial. If not – please read it. At that article you will find how spring data elasticsearch package was used previously – within 3d and 4th versions. Here I will try to provide several examples how it was in the past and how query builders are used since 5th version. Lets start from simple range query example. He how we did it in the past:

//! some imports omitted for readability !
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;

public class HotelAgeFilter {
    public static AbstractQueryBuilder createFilter(HotelSearchCriteria criteria) {
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age")
                .from(criteria.getHotelAge())
                .includeLower(true);

        return rangeQueryBuilder;
    }
}

And here is the new approach:

//! some imports omitted for readability !

import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryVariant;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.json.JsonData;

public class HotelAgeFilter {
    public static Query createFilter(HotelSearchCriteria criteria) {
        QueryVariant rangeAgeQuery = new RangeQuery.Builder()
                .field("age")
                .gte(JsonData.of(criteria.getHotelAge()))
                .build();

        return new Query(rangeAgeQuery);
    }
}

Lets analyse the changes together. First of all “import namespaces” are completely another one. And at the second, the way query builder are used has changed drastically. Moreover, It is still hard to find some examples at internet – I really had to experiment a lot to force it to become working. The main thing that was changed is the approach of getting builders. Previously we had universal QueryBuilders class from which we got builders for different types of Elasticsearch queries. From 5x version builders are moved inside Elasticsearch “query” classes. Lets view more complicated example with using bool condition. So, at 1st lets take example from the previous spring data Elasticsearch package version:

//! some imports omitted for readability !

import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

public class CityNameFilter {
    public static AbstractQueryBuilder createFilter(HotelSearchCriteria criteria) {
        MatchQueryBuilder matchQueryFirst = QueryBuilders.matchQuery(
        "cityNameEn",
         criteria.getCityName()
        )
        .fuzziness(Fuzziness.TWO);

        MatchQueryBuilder matchQuerySecond = QueryBuilders.matchQuery(
                "cityNameEn",
                "London"
        );

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .should(matchQueryFirst)
                .should(matchQuerySecond);

        return boolQueryBuilder;
    }
}

And here is the way we can do it at spring boot 3 with spring Elasticsearch 5.x:

//! some imports omitted for readability !

import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryVariant;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;

public class CityNameFilter {
    public static Query createFilter(HotelSearchCriteria criteria) {
        QueryVariant matchQueryFirst = new MatchQuery.Builder()
                .field("cityNameEn")
                .query(criteria.getCityName())
                .fuzziness("2")
                .build();

        QueryVariant matchQuerySecond = new MatchQuery.Builder()
                .field("cityNameEn")
                .query("London")
                .build();

        BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
        boolQueryBuilder.should(new Query(matchQueryFirst));
        boolQueryBuilder.should(new Query(matchQuerySecond));

        return new Query(boolQueryBuilder.build());
    }
}

As you see, Builder again appeared inside MatchQuery class. Pay attention also how Bool Query Builder is used now. There were a lot of other changes at advanced search system microservice skeleton, that we has build together via 7 part series of articles at “Java Spring Boot Elasticsearch tutorial“, e.g NativeSearchQuery was replaced with new NativeQuery class, springfox swagger was replaced with OpenAPI 3 package, and so on. All that changes are available within last huge project upgrade video lectures at my on line course at udemy, which you can easily enroll at using button below using coupon with best possible price


About the author

sergii-demianchuk

Software engineer with over 18 year’s experience. Everyday stack: PHP, Python, Java, Javascript, Symfony, Flask, Spring, Vue, Docker, AWS Cloud, Machine Learning, Ansible, Terraform, Jenkins, MariaDB, MySQL, Mongo, Redis, ElasticSeach

architecture AWS cluster cyber-security devops devops-basics docker elasticsearch flask geo high availability java machine learning opensearch php programming languages python recommendation systems search systems spring boot symfony