DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Set Up Spring Data Elasticsearch With Basic Authentication
  • Upgrade Guide To Spring Data Elasticsearch 5.0
  • Keep Your Search Cluster Fit: Essential Health Checks to Keep Elasticsearch Healthy
  • Building a Cost-Effective ELK Stack for Centralized Logging

Trending

  • LLM Agents and Getting Started with Them
  • Docker Hardened Images Are Free Now — Here's What You Still Need to Build
  • AI Paradigm Shift: Analytics Without SQL
  • 5 Common Security Pitfalls in Serverless Architectures
  1. DZone
  2. Data Engineering
  3. Databases
  4. Introduction to Spring Data Elasticsearch 5.5

Introduction to Spring Data Elasticsearch 5.5

Getting started with the latest version of Spring Data Elasticsearch 5.5 and Elasticsearch 8.18 as a NoSQL database for our data storage.

By 
Arnošt Havelka user avatar
Arnošt Havelka
DZone Core CORE ·
Oct. 10, 25 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
2.9K Views

Join the DZone community and get the full member experience.

Join For Free

It's been a while since my first article dedicated to Spring Data Elasticsearch usage as a NoSQL database was published. A couple of articles with configuration changes or hints followed the first article. Therefore, the main goal of this article is to define a new baseline for the full Elasticsearch setup.

Note: All previous articles are listed at the end.

In this article, you will learn the following:

  • How to set up an unsecured Elasticsearch and ElasticHQ with Docker images
  • How to configure and use Spring Data Elasticsearch in our project 
  • How to expose a REST API for our data stored in Elasticsearch

Let's start with the setup of an unsecured Elasticsearch via Docker.

Set Up Elasticsearch

All upgrade notes/hints for Spring Data Elasticsearch in version 5.5.x can be found here. The latest technologies used in this article, compliant with the compatibility matrix, are:

  1. Spring Data Elasticsearch 5.5.4
  2. Spring Boot 3.5.6
  3. Elasticsearch 8.18.6

The unsecured Elasticsearch, including ElasticHQ, can be started easily with the Docker images available at https://hub.docker.com/.

Elasticsearch

Create Custom Network

First, we need to define our network to be used for connecting all desired services together. In this article, we connect Elasticsearch and ElasticHQ only. The network is called sat-elk-net.

Shell
 
docker network create sat-elk-net


Run Unsecured Elasticsearch Docker Image

Elasticsearch can be started from the Docker image (see https://hub.docker.com/_/elasticsearch) with these arguments:

  • name – Define the desired Docker container name
  • net – Connect to the defined network (created in the first step)
  • p – Remap Elasticsearch ports (to be accessible from the outside of the Docker container)
  • discovery.type=single-node – Use a single-node cluster for our purpose (see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docker.html#docker-cli-run-dev-mode)
  • xpack.security.enabled=false – Disable X-Pack security in order to have unsecured Elasticsearch
  • elasticsearch:<TAG> – Define the Docker image name and the desired version
Shell
 
docker run -d \
    --name sat-elasticsearch-unsecured \
    --net sat-elk-net \
    -p 9200:9200 -p 9300:9300 \
    -e "discovery.type=single-node" \
    -e "xpack.security.enabled=false" \
    elasticsearch:8.18.6


Alternatively, when xpack.security.enabled parameter is not used at creation time so the XPack security in Elasticsearch can be disabled anytime with these commands:

Shell
 
docker exec -it <CONTAINER_ID> bash
cd /usr/share/elasticsearch/config
echo "xpack.security.enabled: false" >> elasticsearch.yml


Verification

The Elasticsearch running locally can be verified by this CURL command:

Shell
 
curl http://localhost:9200/


The expected output looks, e.g., like this:

JSON
 
{
  "name" : "2e40b9ac8236",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "PZM1IcPMQpWv_XhyaNqpfg",
  "version" : {
    "number" : "8.18.6",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "970b6c3ae853753ae66a12c1208c85a3c9728d92",
    "build_date" : "2025-08-25T22:05:47.180118464Z",
    "build_snapshot" : false,
    "lucene_version" : "9.12.1",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}


ElasticHQ

ElasticHQ is a simplified monitoring and management tool for Elasticsearch clusters. Again, we can start it easily with this command (the arguments are pretty similar to the previous command):

Shell
 
docker run -d --name sat-elastichq --net sat-elk-net -p 5000:5000 elastichq/elasticsearch-hq


Verification

The correct integration between ElasticHQ and Elasticsearch can be verified by putting http://localhost:5000/#!/ in our browser and using the correct URL to the created Elasticsearch cluster (just a single node in our case).

Connect to Elasticsearch

Note: We need to modify the "Connect to Elasticsearch" URL in case it's not running locally.

By clicking the Connect button, we should see a screen like this:

Click the Connect button

Set Up Spring Data Elasticsearch

Adding Spring Data Elasticsearch to our application is really straightforward. Besides the Maven dependency, there's also a configuration class defining the connection to Elasticsearch and the document mapping. The new configuration class is introduced for readiness for future extensions and changes. 

Maven Dependency

First, we need to add spring-boot-starter-data-elasticsearch dependency in our Maven project (pom.xml) first. The latest available version can be found in the Maven Central repository.

XML
 
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
	<version>3.5.6</version>
</dependency>


Elasticsearch Client

Next, we should configure the Elasticsearch client in our application. For that, we have a new property group (in the configuration), the mapping Spring configuration class for our configuration group, and the Spring configuration class for our connection to Elasticsearch.

YAML Configuration

In order to have flexible Elasticsearch configuration, we enhance the application.yaml file with a new elk configuration group (any other meaningful name would work as well) containing these properties:

  • host – Used to define the host name where Elasticsearch is running (line 2). Note: the previous articles used spring.elasticsearch.uris configuration item for the same purpose.
  • security-enabled – Used for switching the security on and off (line 3). Currently, it's commented out (using false default value).
  • username and password – Represents the user credential used for the authentication (lines 4-5).

The new configuration group looks like this:

YAML
 
elk:
  host: localhost
  # security-enabled: false
  # username: elastic
  # password: elastic


Note: As you can see, most of the configuration items are commented out (lines 3-5) as they will be used in the next article (dedicated to using BASIC authentication). Our current concern here is to connect to the unsecured Elasticsearch, and just the host property is sufficient for that purpose.

Spring Configuration

Additionally, we add a properties class in order to simplify the configuration usage in our project. ElasticsearchProperties class is used for loading all the properties from the elk configuration group (line 2) as:

Java
 
@Configuration
@ConfigurationProperties( prefix = "elk" )
@Getter
@Setter
public class ElasticsearchProperties {

    private boolean securityEnabled;
    private String host;
    private String username;
    private String password;

}


Unsecured Elasticsearch Configuration

The most important part of the client setup is located in the ElasticsearchUnsecuredConfig  configuration class defined as:

  1. The configuration is effective only when the security is disabled in our configuration, see elk.security-enabled=false (line 2).
  2. The ElasticsearchProperties instance is injected (line 7) to access all the configuration properties.
  3. A new bean for the ClientConfiguration class is defined (lines 10-14) with the desired configuration. Currently, just the host of the Elasticsearch is used here.
Java
 
@Configuration
@ConditionalOnProperty(name = "elk.security-enabled", havingValue = "false")
@RequiredArgsConstructor
public class ElasticsearchUnsecuredConfig extends ElasticsearchConfiguration {

	@Getter
	private final ElasticsearchProperties elkProperties;

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder()
				.connectedTo(elkProperties.getHost())
				.build();
	}
	
}


Verification

When our Elasticsearch is running, we should be able to start our application and see an output like this:

Plain Text
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.3)

2025-06-10T08:47:56.151+02:00  INFO 224 --- [sat-elk] [  restartedMain] com.github.aha.sat.elk.ElkApplication    : Starting ElkApplication using Java 23 with PID 6224 (<spring-advanced-training>\sat-elk\target\classes started by hh310 in Z:\work\aha\gitrepos\spring-advanced-training\sat-elk)
2025-06-10T08:47:56.155+02:00  INFO 224 --- [sat-elk] [  restartedMain] com.github.aha.sat.elk.ElkApplication    : No active profile set, falling back to 1 default profile: "default"
2025-06-10T08:47:56.228+02:00  INFO 224 --- [sat-elk] [  restartedMain] m.e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-06-10T08:47:56.228+02:00  INFO 224 --- [sat-elk] [  restartedMain] m.e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2025-06-10T08:47:57.114+02:00  INFO 224 --- [sat-elk] [  restartedMain] m.s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Elasticsearch repositories in DEFAULT mode.
2025-06-10T08:47:57.189+02:00  INFO 224 --- [sat-elk] [  restartedMain] m.s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 66 ms. Found 1 Elasticsearch repository interface.
2025-06-10T08:47:57.735+02:00  INFO 224 --- [sat-elk] [  restartedMain] mo.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-06-10T08:47:57.757+02:00  INFO 224 --- [sat-elk] [  restartedMain] mo.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-10T08:47:57.758+02:00  INFO 224 --- [sat-elk] [  restartedMain] mo.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.41]
2025-06-10T08:47:57.816+02:00  INFO 224 --- [sat-elk] [  restartedMain] mo.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-06-10T08:47:57.816+02:00  INFO 224 --- [sat-elk] [  restartedMain] mw.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1587 ms
2025-06-10T08:47:58.663+02:00  INFO 224 --- [sat-elk] [  restartedMain] mo.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2025-06-10T08:47:59.945+02:00  INFO 224 --- [sat-elk] [  restartedMain] mo.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-06-10T08:47:59.954+02:00  INFO 224 --- [sat-elk] [  restartedMain] mcom.github.aha.sat.elk.ElkApplication    : Started ElkApplication in 4.401 seconds (process running for 5.922)


City Domain

Our sat-elk application contains a city domain. Therefore, we can demonstrate Spring Data Elasticsearch usage there. The data in this domain is persisted with the City class which represents a mapping class to the document in Elasticsearch (it's an equivalent of JPA entity for Elasticsearch). The city domain provides simple CRUD operations in order to manage cities. The main features demonstrated here are:

  • Upload cities as a CSV file
  • Get city details
  • Search for cities

Before we start, let's shed light on the already mentioned document and its mapping.

Document Mapping

All Spring Data projects are based on a standard mapping of data into a POJO class in order to provide simple and consistent persistence support and access to the data in the desired technology (the Elasticsearch in our case). 

The simple City class represents an Elasticsearch document and maps all the fields from the CSV file to these attributes:
Document mapping

The class has these annotations in order to define necessary metadata (mainly for Elasticsearch purposes):

  • Document – To mark the class as the Elasticsearch document. Here, it specifies just an index name to define a storage in the Elasticsearch cluster. Note: There are more configuration options, but it's out of the scope of this article.
  • Id – To specify the primary key of the document (usually it should be String for NoSQL technology).
  • Field – To specify custom mapping (e.g., data type, data format, for sorting, etc.) when it is necessary. As you can see, there's no need to define it all the time. It's demonstrated here only on the name attribute.
  • JsonProperty – This annotation is not relevant for Elasticsearch mapping. It's needed by the jackson-dataformat-csv library to support operations with CSV files.
Java
 
import java.io.Serializable;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Document(indexName = City.INDEX)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class City implements Serializable {

	private static final long serialVersionUID = 1L;

	public static final String INDEX = "city";

	@Id
	private String id;

	@JsonProperty("name")
	@Field(type = FieldType.Text, fielddata = true)
	private String name;

	@JsonProperty("country")
	private String country;

	@JsonProperty("subcountry")
	private String subcountry;

	@JsonProperty("geonameid")
	private Long geonameid;

}


More information about mapping details and their usage can be found in the Spring Data reference documentation or the Elasticsearch reference documentation.

Repository

Finally, we need to create a repository serving as an adapter to the Elasticsearch cluster for reading and storing data. For this purpose, we have a CityRepository interface defined as:

  • It extends ElasticsearchRepository (line 5) in order to inherit all common CRUD methods (e.g., from CrudRepository). We just need to specify the used document type (City class in our case) and the primary key type (String as already mentioned).
  • The repository can be empty (without any method), and it would work for reading and storing data (as already mentioned). However, the findByCountry method is added (line 7) in order to demonstrate the ease of defining a specific search method.
Java
 
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface CityRepository extends ElasticsearchRepository<City, String> {

	Page<City> findByCountry(String country, Pageable pageable);

}


Elasticsearch Usage

Now, it's time to add some REST endpoints utilizing the previously prepared components to connect to the Elasticsearch. There are three main features demonstrated here:

  • Upload data from a CSV file
  • Get a city detail
  • Search for cities

Upload Data

As the fresh new Elasticsearch created in previous steps is still empty, we need to add some data into it. This data is based on open data from major cities across the world. The world-cities.csv file available here contains approximately. 26k cities (at least when it was downloaded by me last time). Its structure is very simply defined as:

Plain Text
 
name,country,subcountry,geonameid
les Escaldes,Andorra,Escaldes-Engordany,3040051
Andorra la Vella,Andorra,Andorra la Vella,3041563
...


The key part of the upload feature lies in the service component where an uploadFile method (lines 17-23) parses a CSV file (lines 25–37) and stores the parsed data in chunks (lines 39-50) into the Elasticsearch cluster. We use the saveAll method in our CityRepository in order to store all entities in the chunk altogether. The chunk size is 500 (see BULK_SIZE on line 6).

Most of this processing is out of the scope of this article. The real concern here is storing data in an Elasticsearch cluster realized by repository.saveAll(...) method (line 47).

Java
 
@Service
@RequiredArgsConstructor
@Slf4j
public class CityService {

	private static final int BULK_SIZE = 500;

	final CityRepository repository;
	final ElasticsearchOperations esTemplate;
	final CsvMapper csvMapper = new CsvMapper();

	final CsvSchema schema = csvMapper
			.typedSchemaFor(City.class)
			.withHeader()
			.withColumnReordering(true);

	public void uploadFile(String csvFileName) {
		log.info("loading file {} ...", csvFileName);
		List<City> csvData = parseFile(csvFileName);
		log.info("{} entries loaded from CSV file", csvData.size());
		storeData(csvData);
		log.info("data loading finish");
	}

	List<City> parseFile(String csvFileName) {
		try {
			var csvFile = Path.of(csvFileName);
			return csvMapper
					.disable(FAIL_ON_MISSING_HEADER_COLUMNS)
					.readerFor(City.class)
					.with(schema)
					.<City>readValues(csvFile.toFile())
					.readAll();
		} catch (IOException e) {
			throw new ElkException(e);
		}
	}

	private void storeData(List<City> cities) {
		final var counter = new AtomicInteger();

		final Collection<List<City>> chunks = cities.stream()
				.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / BULK_SIZE))
				.values();
		counter.set(0);
		chunks.forEach(ch -> {
			repository.saveAll(ch);
			log.info("bulk of cities stored [{}/{}] ...", counter.getAndIncrement(), chunks.size());
		});
	}

}


Of course, there has to be an endpoint to expose the upload feature. The new API is added by uploadFile method into theCityController class as: 

Java
 
@RestController
@RequestMapping(value = CityController.ROOT_PATH, produces = APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class CityController {

	static final String ROOT_PATH = "/api/cities";

	final CityService service;

	@PostMapping("/upload")
	@ResponseStatus(code = NO_CONTENT)
	public void uploadFile(String filename) {
		service.uploadFile(filename);
	}

}


We can verify our first REST endpoint here by uploading a CSV file via the POST (HTTP) method on /api/cities/upload path as:

Shell
 
http://localhost:8080/api/cities/upload?filename=<PATH_TO_FILE>/world-cities.csv


The application logs from the data upload look like this:

Shell
 
2025-09-24T08:32:05.653+02:00  ... CityService    : loading file Z:/work/aha/dev/world-cities.csv ...
2025-09-24T08:32:05.755+02:00  ... CityService    : 26467 entries loaded from CSV file
2025-09-24T08:32:06.330+02:00  ... CityService    : bulk of cities stored [0/53] ...
2025-09-24T08:32:06.561+02:00  ... CityService    : bulk of cities stored [1/53] ...
...
2025-09-24T08:32:12.550+02:00  ... CityService    : bulk of cities stored [52/53] ...
2025-09-24T08:32:12.551+02:00  ... CityService    : data loading finished


The existence of the uploaded data can also be verified in the ElasticHQ. We can see changes in our Elasticsearch cluster. One new index is added with more than 26k documents. 

ElasticHQ

Retrieve Data

Reading data from our Elasticsearch cluster via Spring Data Elasticsearch is very easy, convenient, and straightforward. It's similar to the usage of Spring Data for any other technology (e.g., JPA or Redis). The article covers retrieving data for:

  • Get a city detail based on the given city ID
  • Search for cities in the country with static and dynamic queries

Get One City

One of the most important features, when working with data, is getting the document details. We don't need to implement anything on the repository level in order to retrieve a single document requested by its ID. Spring Data provides thefindById method for us in their CrudRepository class..

Our CityService class is straightforward and provides findById method as: 

Java
 
public City findById(String cityId) {
	return repository.findById(cityId).orElseThrow(() -> new ElkException("City with ID=" + cityId + " was not found!"));
}


As it could be mentioned in the CrudRepository class, the findById method returns Optional<T>. Therefore, we need to decide how to handle this situation in our service layer. We just throw an exception when the city instance is not found by the given ID.

In order to expose it as a REST API, we add the getById method in the CityController class as:

  • The endpoint is exposed on http://localhost:8080/api/cities/{id} URL (line 1). The desired document ID is passed as {id} in the path.
  • The findById method is delegated to the service (see line 3).
Java
 
@GetMapping("/{id}")
public City getById(@PathVariable String id) {
	return service.findById(id);
}


To test our new feature, we can call http://localhost:8080/api/cities/mv9sepkB_odA0NUSFQaT and see the response like this:

JSON
 
{
  "id": "mv9sepkB_odA0NUSFQaT",
  "name": "Beroun",
  "country": "Czechia",
  "subcountry": "Central Bohemia",
  "geonameid": 3079467
}


Note: The used ID was picked up from a find response implemented in the next step.

Find Cities with Static Query

When we want to search cities, the simplest approach is to use a "static" query. 

The static query means the defined method in our repository interface. Therefore the search is driven and using all the passed arguments (we cannot change them dynamically). It's very efficient as we don't need to write a single line (we only need to define the desired method correctly). The Spring Data translates the method name into a query implementation automatically for us. More information is available on naming convention page.

In our case, we have the findByCountry method in CityRepository class (see above) like this: 

Java
 
Page<City> findByCountry(String country, Pageable pageable);


The rest of the search (service and controller) is straightforward. We extend our CityService class with searchByCountry method as: 

Java
 
public Page<City> searchByCountry(String country, Pageable pageable) {
	return repository.findByCountry(country, pageable);
}


And add searchByCountry method in CityController class as:

Java
 
@GetMapping("/country/{country}")
public Page<City> searchByCountry(@PathVariable("country") String country, Pageable pageable) {
	return service.searchByCountry(country, pageable);
}


The static search feature (to find all cities, e.g., from the Czech Republic) can be verified on http://localhost:8080/api/cities/country/czechia?sort=name,desc with this output:

JSON
 
{
  "content": [
    {
      "id": "QP9sepkB_odA0NUSFQaS",
      "name": "Žďár nad Sázavou",
      "country": "Czechia",
      "subcountry": "Vysočina",
      "geonameid": 3061695
    },
    ...
    {
      "id": "Sf9sepkB_odA0NUSFQaS",
      "name": "Uherské Hradiště",
      "country": "Czechia",
      "subcountry": "Zlín",
      "geonameid": 3063739
    }
  ],
  "page": {
    "size": 20,
    "number": 0,
    "totalElements": 96,
    "totalPages": 5
  }
}


Search for Cities With Dynamic Query

The static query is very easy to use, but it isn't sufficient in many cases. Sometimes, we need a combination of different arguments, or we want to ignore some argument when it doesn't contain a desired value. The "dynamic query" term is used here when there's a need for adjusting the query according to the desired search criteria (e.g., name or country attribute here).

For the dynamic query feature, we need to add a search method into the CityService class which just wraps and simplifies the real logic provided by the search method located in the ElasticsearchOperations class. Our search method in the CityService class has three steps:

  • First, we trigger a searchHits method (line 3) to search for the data in the Elasticsearch.
  • Next, the found result is  converted from the SearchHits to SearchPage (line 4).
  • Finally, the SearchPage is converted again, but this time it's converted to the desired Page<City> type (line 5).
Java
 
@SuppressWarnings("unchecked")
public Page<City> search(String name, String country, String subcountry, Pageable pageable) {
	var result = searchHits(name, country, subcountry, pageable);
	var searchPage = SearchHitSupport.searchPageFor(result, pageable);
	return (Page<City>) SearchHitSupport.unwrapSearchHits(searchPage);
}


The key part in the search plays searchHits method with this behavior:

  • First, a Query instance is built by the buildSearchQuery method (line 4).
  • Next, we assign the pageable instance to the new query (line 5) in order to restrict our search accordingly.
  • Finally, we trigger the search on esTemplate instance (i.e., the ElasticsearchOperations class) by passing our query instance and City.class (the desired document class).
Java
 
ElasticsearchOperations esTemplate;

SearchHits<City> searchHits(String name, String country, String subcountry, Pageable pageable) {
	CriteriaQuery query = buildSearchQuery(name, country, subcountry);
	query.setPageable(pageable);
	return esTemplate.search(query, City.class);
}


The buildSearchQuery method (as mentioned above) evaluates all passed arguments (e.g., lines 3-5 for the name argument) and adds the desired expression to the criteria query. Here, we can use expressions like is, contains, and, not, expression, etc. More details on constructing the query can be found in the reference documentation.

Java
 
private CriteriaQuery buildSearchQuery(String name, String country, String subcountry) {
	var criteria = new Criteria();
	if (name!=null) {
		criteria.and(new Criteria("name").contains(name));
	}
	if (country!=null) {
		criteria.and(new Criteria("country").expression(country));
	}
	if (subcountry!=null) {
		criteria.and(new Criteria("subcountry").is(subcountry));
	}
	return new CriteriaQuery(criteria);
}


Note: Of course, we can move the buildSearchQuery logic into CityRepository class. It's just my personal preference to have it here.

The last piece in this feature is extending our controller with the new REST endpoint. Exposing REST API for it in the CityController class is similar to the static query. You can find such code in the search method available below.

Java
 
@GetMapping
public Page<City> search(@RequestParam(name = "name", required = false) String name,
		@RequestParam(name = "country", required = false) String country,
		@RequestParam(name = "subcountry", required = false) String subcountry, Pageable pageable) {
	return service.search(name, country, subcountry, pageable);
}


The new API for dynamic search can be verified on http://localhost:8080/api/cities?name=be&country=czechia&subcountry=bohemia&size=5&sort=name with this output:

JSON
 
{
  "content": [
    {
      "id": "m_9sepkB_odA0NUSFQaT",
      "name": "Benešov",
      "country": "Czechia",
      "subcountry": "Central Bohemia",
      "geonameid": 3079508
    },
    {
      "id": "mv9sepkB_odA0NUSFQaT",
      "name": "Beroun",
      "country": "Czechia",
      "subcountry": "Central Bohemia",
      "geonameid": 3079467
    },
    {
      "id": "lv9sepkB_odA0NUSFQaT",
      "name": "Brandýs nad Labem-Stará Boleslav",
      "country": "Czechia",
      "subcountry": "Central Bohemia",
      "geonameid": 3078837
    }
  ],
  "page": {
    "size": 5,
    "number": 0,
    "totalElements": 3,
    "totalPages": 1
  }
}


Conclusion

This article has covered the basics of Spring Cloud Elasticsearch usage with Spring Data Elasticsearch 5.5 and Elasticsearch 8.18. We began with the preparation of an Elasticsearch cluster (in development mode) and configured Spring Data in our project. Next, all the important classes used by Spring Data Elasticsearch (e.g., the repository and City document) were created. In the end, we implemented REST endpoints for uploading and searching city data in our Elasticsearch cluster. The complete source code demonstrated above is available in my GitHub repository.

In the next article, the usage of Spring Data Elasticsearch with basic authentication will be covered.

Note: The additional application details can be found in these previous articles:

  • Upgrade Guide to Spring Data Elasticsearch 5.0 
  • Pagination With Spring Data Elasticsearch 4.4 
  • Introduction to Spring Data Elasticsearch 4.1
Elasticsearch Spring Data Data (computing)

Opinions expressed by DZone contributors are their own.

Related

  • Set Up Spring Data Elasticsearch With Basic Authentication
  • Upgrade Guide To Spring Data Elasticsearch 5.0
  • Keep Your Search Cluster Fit: Essential Health Checks to Keep Elasticsearch Healthy
  • Building a Cost-Effective ELK Stack for Centralized Logging

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook