Spring Boot Reactive Elasticsearch Example

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

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.

spring boot elasticsearch example

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.

spring boot elasticsearch example

Get API

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

spring boot elasticsearch example

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.

spring boot elasticsearch example

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.

Spring Boot Reactive Elasticsearch Example
Scroll to top

Discover more from ASB Notebook

Subscribe now to keep reading and get access to the full archive.

Continue reading