Integrating Spring Boot with Elasticsearch for Cross-Version Compatibility

Elasticsearch Overview

Elasticsearch is a search server built on top of Lucene, providing REST API interfaces for full-text search capabilities. As a highly scalable open-source search and analytics engine, it enables fast storage, search, and analysis of large datasets.

Key characteristics include distributed architecture, high availability, asynchronous writing, multiple APIs, and document-oriented design.

Core concepts encompass near real-time processing, clusters, nodes for data storage, indices, shards for distributing indices, and replicas for redundancy.

Common use cases include Wikipedia, Stack Overflow, and GitHub.

Version Compatibility Matrix

When integrating Spring Boot with Elasticsearch, version alignment is critical.

Spring Boot Version Spring Data Elasticsearch Elasticsearch Version
1.3.5 or lower 1.3.4 or lower 1.7.2 or lower
1.4.x or higher 2.0.0 to 5.0.0 2.0.0 to 5.0.0

This demonstration uses Spring Boot 1.5.9 with Elasticsearch 2.3.5.

Spring Data Elasticsearch Integration

Maven Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    <version>1.5.9.RELEASE</version>
</dependency>

Configuration

spring.data.elasticsearch.repositories.enabled=true
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300

Note: Port 9300 is for Java client communication, while 9200 exposes the RESTful HTTP interface.

Additional configuration options:

# Cluster name (default: elasticsearch)
spring.data.elasticsearch.cluster-name=elasticsearch
# Comma-separated list of cluster nodes
spring.data.elasticsearch.cluster-nodes=host1:9300,host2:9300
# Additional client properties
spring.data.elasticsearch.properties=
# Enable repositories (default: true)
spring.data.elasticsearch.repositories.enabled=true

Domain Entity

@Document(indexName = "productindex", type = "product")
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long productId;
    private String productName;
    private Integer price;
    private String productDesc;
    private String createTime;

    // getters and setters omitted
}

When using Spring Data, the entity requirse indexName (equivalent to a database) and type (equivalent to a table). Both must be lowercase.

Repository Layer

public interface ProductDao extends ElasticsearchRepository<Product, Long> {
}

The repository inehrits standard CRUD operations from ElasticsearchRepository. The save method handles both insert and update (upsert behavior), delete removes data and indices, and search supports various query types including pagination and scoring.

Service Layer

@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductDao productDao;

    @Override
    public boolean saveProduct(Product product) {
        boolean success = false;
        try {
            productDao.save(product);
            success = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return success;
    }

    @Override
    public List<Product> fullTextSearch(String keyword) {
        QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder(keyword);
        System.out.println("Query: " + queryBuilder);
        Iterable<Product> results = productDao.search(queryBuilder);
        Iterator<Product> iterator = results.iterator();
        List<Product> productList = new ArrayList<>();
        while (iterator.hasNext()) {
            productList.add(iterator.next());
        }
        return productList;
    }

    @Override
    public List<Product> paginatedSearch(int pageNum, int pageSize, String keyword) {
        Pageable pageable = new PageRequest(pageNum, pageSize);
        QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder(keyword);
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(pageable)
                .withQuery(queryBuilder)
                .build();
        System.out.println("Query: " + searchQuery.getQuery().toString());
        Page<Product> results = productDao.search(searchQuery);
        return results.getContent();
    }

    @Override
    public List<Product> weightedSearch(String keyword) {
        FunctionScoreQueryBuilder scoreQuery = QueryBuilders.functionScoreQuery()
                .add(QueryBuilders.boolQuery()
                        .should(QueryBuilders.matchQuery("productName", keyword)),
                    ScoreFunctionBuilders.weightFactorFunction(10))
                .add(QueryBuilders.boolQuery()
                        .should(QueryBuilders.matchQuery("productDesc", keyword)),
                        ScoreFunctionBuilders.weightFactorFunction(100))
                .setMinScore(2);
        System.out.println("Query: " + scoreQuery.toString());
        Iterable<Product> results = productDao.search(scoreQuery);
        Iterator<Product> iterator = results.iterator();
        List<Product> productList = new ArrayList<>();
        while (iterator.hasNext()) {
            productList.add(iterator.next());
        }
        return productList;
    }
}

Query types include full-text search, paginated search, and weighted scoring. Weighted queries prioritize results based on assigned scores—higher weights rank first, while items below setMinScore are excluded from results.

API Testing

Insert Data:

POST http://localhost:8080/api/product
{"productId":1,"productName":"ItemA","price":299,"productDesc":"ItemA is an electronics product","createTime":"2024-01-15 10:30:00"}
{"productId":2,"productName":"ItemB","price":599,"productDesc":"ItemB is a clothing product","createTime":"2023-11-20 14:45:00"}
{"productId":3,"productName":"ItemC","price":199,"productDesc":"ItemC is a food product","createTime":"2024-03-10 09:15:00"}

Full-Text Search:

GET http://localhost:8080/api/product?keyword=electronics

Paginated Search:

GET http://localhost:8080/api/product?pageNum=0&pageSize=2&keyword=product

Weighted Search:

GET http://localhost:8080/api/product/weighted?keyword=ItemB

After inserting data, access the head plugin at http://localhost:9200/_plugin/head/ to visualize and query data.

Alternative Approaches

Beyond Spring Data, Elasticsearch can be accessed via:

  1. TransportClient - Native Elasticsearch API (version-dependent)
  2. ElasticsearchTemplate - Spring wrapper for direct bean injection
  3. JestClient - HTTP-based client that works with Elasticsearch 2.x and above without version-specific code changes

Using JestClient for Version-Agnostic Integration

JestClient wraps Elasticsearch HTTP REST interfaces, eliminating the need to rewrite client code when server versions change.

Maven Dependency

<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>5.3.3</version>
</dependency>

Implementation Example

import io.searchbox.client.JestClient;
import io.searchbox.client.JestClientFactory;
import io.searchbox.client.JestResult;
import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.core.*;
import io.searchbox.indices.*;

public class ElasticsearchClientDemo {
    private static JestClient client;
    private static final String INDEX = "productindex";
    private static final String TYPE = "product";
    private static final String ES_URL = "http://127.0.0.1:9200";

    public static void main(String[] args) throws Exception {
        client = createClient();
        batchInsert();
        executeFullTextSearch();
        executeTermSearch();
        executeRangeSearch();
        client.close();
    }

    private static JestClient createClient() {
        JestClientFactory factory = new JestClientFactory();
        factory.setHttpClientConfig(
            new HttpClientConfig.Builder(ES_URL)
                .connTimeout(60000)
                .readTimeout(60000)
                .multiThreaded(true)
                .build());
        return factory.getObject();
    }

    private static void batchInsert() throws Exception {
        List<Object> items = new ArrayList<>();
        items.add(new Product(1L, "ItemA", 299, "Electronics product", "2024-01-15 10:30:00"));
        items.add(new Product(2L, "ItemB", 599, "Clothing product", "2023-11-20 14:45:00"));
        items.add(new Product(3L, "ItemC", 199, "Food product", "2024-03-10 09:15:00"));
        
        Bulk.Builder bulkBuilder = new Bulk.Builder()
            .defaultIndex(INDEX)
            .defaultType(TYPE);
        for (Object item : items) {
            bulkBuilder.addAction(new Index.Builder(item).build());
        }
        BulkResult result = client.execute(bulkBuilder.build());
        System.out.println("Batch insert: " + result.isSucceeded());
    }

    private static void executeFullTextSearch() throws Exception {
        String query = "product";
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.queryStringQuery(query));
        sourceBuilder.from(0).size(2);
        System.out.println("Full-text query: " + sourceBuilder.toString());
        System.out.println("Results: " + executeSearch(sourceBuilder.toString()));
    }

    private static void executeTermSearch() throws Exception {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.termQuery("price", 299));
        System.out.println("Term query: " + sourceBuilder.toString());
        System.out.println("Results: " + executeSearch(sourceBuilder.toString()));
    }

    private static void executeRangeSearch() throws Exception {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.rangeQuery("price").gte(200).lte(500));
        System.out.println("Range query: " + sourceBuilder.toString());
        System.out.println("Results: " + executeSearch(sourceBuilder.toString()));
    }

    private static String executeSearch(String query) throws Exception {
        Search search = new Search.Builder(query)
            .addIndex(INDEX)
            .addType(TYPE)
            .build();
        JestResult result = client.execute(search);
        return result.getSourceAsString();
    }
}

Sample Results

Full-Text Search:

{
  "from": 0,
  "size": 2,
  "query": {
    "query_string": {
      "query": "product"
    }
  }
}
// Returns matching documents with pagination

Term Search:

{
  "query": {
    "term": {
      "price": 299
    }
  }
}
// Returns documents matching exact price value

Range Search:

{
  "query": {
    "range": {
      "price": {
        "from": 200,
        "to": 500,
        "include_lower": true,
        "include_upper": true
      }
    }
  }
}
// Returns documents within price range

Elasticsearch Installation on Windows

  1. Download from https://www.elastic.co/downloads - select the ZIP distribution for your target version

  2. Start Elasticsearch - Run bin/elasticsearch.bat and verify at http://localhost:9200

  3. Install Head Plugin - Execute plugin install mobz/elasticsearch-head from the bin directory, then access at http://localhost:9200/_plugin/head/

  4. Register as Windows Service - Run service.bat install followed by service.bat start from the bin directory, then verify via services.msc

Updated Version Support

For Spring Boot 2.x environments with newer Elasticsearch versions, use Spring Data Elasticsearch 3.x:

Spring Data Elasticsearch Elasticsearch Version
3.2.x 6.7.2
3.1.x 6.2.2
3.0.x 5.5.0

Official documentation: https://github.com/spring-projects/spring-data-elasticsearch

Tags: Spring Boot elasticsearch Spring Data JestClient Search Engine

Posted on Mon, 31 Aug 2026 16:45:33 +0000 by mindfield