Elasticsearch Example using Spring Boot Reactive

Thameem Ansari
Javarevisited
Published in
5 min readJul 5, 2023
Elasticsearch Example using Spring Boot Reactive

Introduction

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.

Technologies used in the article:

  • Spring boot: 2.7.3
  • Elasticsearch: 7.17.4
  • Java: 17

Create a spring boot application

Create a spring boot application with the required dependencies. Add the 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>

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 service 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 Controller class

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. 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.

Create API

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.

Update API

Get API

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

Get API

Delete API

To delete a particular student document, we can use the DELETE API: /students/{id}

Delete API

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.

--

--

Javarevisited
Javarevisited

Published in Javarevisited

A humble place to learn Java and Programming better.

Thameem Ansari
Thameem Ansari

Written by Thameem Ansari

Technology Expert| Coder| Sharing Experience| Spring | Java | Docker | K8s| DevOps| https://reachansari.com