Elasticsearch is a document-based search engine that is fast and widely used. We can use the Spring boot reactive data libraries to interact with the Elasticsearch server while developing a reactive spring application.
In this article, we will learn how to interact with the Elasticsearch server from a reactive spring boot application.
Version details:
- Spring boot: 2.7.3
- Elasticsearch: 7.17.4
- Java: 17
Table of Contents
- Adding the required dependencies
- Adding Elasticsearch configuration
- Creating Elasticsearch documents
- Adding spring data repository
- Adding CRUD implementation
- Adding CRUD APIs
- Testing the CRUD APIs
- Conclusion
We will create a simple Spring boot reactive Elasticsearch application that performs CRUD operations.
Adding the required dependencies
Spring boot framework provides starter dependency spring-boot-starter-data-elasticsearch that we can leverage to interact with the Elasticsearch server.
Since we are creating a reactive style application, we will use the spring-boot-starter-webflux starter dependency.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
Adding Elasticsearch configuration
Next step is to create a configuration class, that extends AbstractReactiveElasticsearchConfiguration class’s reactiveElasticsearchClient() method. Here, we can specify the Elasticsearch connection string and other properties.
This ReactiveElasticsearchClient instance is used by the spring data repositories while performing the CRUD operations.
@Configuration public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration { @Override public ReactiveElasticsearchClient reactiveElasticsearchClient() { final ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo("localhost:9200") .build(); return ReactiveRestClients.create(clientConfiguration); } }
Creating Elasticsearch documents
We will create a document class that stores student details like name, address, subjects, etc.
First, we will create a Student class with details like enrolled subjects and address details, etc.
@Document(indexName = "student-details") @Data public class Student { @Id private String id; @Field(type = FieldType.Text) private String firstName; private String lastName; private int age; @Field(type = FieldType.Date, format = DateFormat.date) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") private LocalDate joinDate; private Address address; @JsonInclude(JsonInclude.Include.NON_NULL) private List<Subject> subjects; }
- We can specify the Elasticsearch document by annotating the class with the @Document annotation. We can also mention the index name of the Elasticsearch document.
- Every Elasticsearch document needs a unique id field that is specified by annotating it with the @Id annotation.
- Elasticsearch supports many document field types, and we can explicitly specify the field type with the help of @Field annotation.
Let’s also create an Address class that holds the student’s address details.
@Data public class Address { private String street; private Integer doorNo; }
Also, create a Subject class as shown below.
@Data @RequiredArgsConstructor public class Subject { private String name; }
Add the below configuration property into the spring boot application’s application.yml file.
By adding this, Jackson library generates Snake case JSON fields while converting the java classes to JSON.
spring: jackson: property-naming-strategy: SNAKE_CASE
Adding spring data repository
Create a StudentRepository interface that extends the ReactiveElasticsearchRepository interface. We can use this repository instance to perform CRUD operations on Elasticsearch documents.
We can also add custom finder methods, as shown below.
public interface StudentRepository extends ReactiveElasticsearchRepository<Student, String> { Flux<Student> findByFirstName(String firstName); }
Adding CRUD implementation
Now let’s create a service layer to add CRUD functionality.
Create a StudentService interface and add the below method signatures.
public interface StudentService { Mono<Student> createStudent(Student student); Mono<Student> updateStudent(String id, Student student); Mono<String> deleteStudent(String id); Flux<Student> getStudentByFirstName(String firstName); Flux<Student> getAllStudents(); }
Create an implementation class and implement the methods defined in the service interface.
We use the Elasticsearch repository to interact with the server and CRUD operations.
@RequiredArgsConstructor @Slf4j @Service public class StudentServiceImpl implements StudentService { private final StudentRepository studentRepository; @Override public Mono<Student> createStudent(Student student) { return studentRepository.save(student); } @Override public Mono<Student> updateStudent(String id, Student student) { return studentRepository.findById(id).flatMap(std -> { log.info("std-{}", std); std.setFirstName(student.getFirstName()); std.setLastName(student.getLastName()); std.setJoinDate(student.getJoinDate()); std.setSubjects(student.getSubjects()); std.setAge(student.getAge()); std.setAddress(student.getAddress()); return studentRepository.save(std); }) .doOnError(e -> log.error(String.valueOf(e))); } @Override public Mono<String> deleteStudent(String id) { return studentRepository.deleteById(id) .thenReturn("Student deleted successfully!"); } @Override public Flux<Student> getStudentByFirstName(String firstName) { return studentRepository.findByFirstName(firstName); } @Override public Flux<Student> getAllStudents() { return studentRepository.findAll(); } }
Adding CRUD APIs
Finally, create the REST endpoints to expose the CRUD APIs, as shown below.
@RequiredArgsConstructor @RestController public class StudentController { private final StudentService studentService; @PostMapping("/students") public Mono<Student> createStudent(@RequestBody Student student){ return studentService.createStudent(student); } @PutMapping("/students/{id}") public Mono<Student> updateStudent(@RequestBody Student student, @PathVariable("id") String id) { return studentService.updateStudent(id, student); } @DeleteMapping("/students/{id}") public Mono<String> deleteStudent(@PathVariable("id") String id){ return studentService.deleteStudent(id); } @GetMapping("/students/{first-name}") public Flux<Student> getStudentByFirstname(@PathVariable("first-name") String firstName) { return studentService.getStudentByFirstName(firstName); } @GetMapping("/students") public Flux<Student> getAllStudents() { return studentService.getAllStudents(); } }
Testing the CRUD APIs
We will use the Postman tool to test our CRUD endpoints.
Run the application. By default, the application starts with port number 8080.
Create API
Invoke the POST API /students with the required student request JSON payload, as shown below.

Update API
To update the saved student document, we can use the PUT API: /students/{id}.
Here, the id is the unique student id generated during the document creation.

Get API
We can fetch all the saved student documents by invoking the GET API: /students.

Delete API
To delete a particular student document, we can use the DELETE API: /students/{id}
Here, the id is the student document’s id.

Conclusion
In this post, we learned how easy it is to create a Spring boot reactive application and perform CRUD operations with Elasticsearch documents.
We also learned how the spring boot framework provides the necessary support for interacting with Elasticsearch by providing the Elasticsearch spring data starter library.
Example code is available on Git hub.