Search Tutorials


Spring Boot + Elasticsearch CRUD Example | JavaInUse

Spring Boot + Elasticsearch CRUD Example

In this tutorial we will be creating a Spring Boot 3 application to perform CRUD operations using Elasticsearch 8. When building applications that interact with an Elasticsearch cluster, ensuring secure and authenticated communication is crucial. In this article, we'll explore how to configure the Elasticsearch client in a Spring Boot application to establish a secure connection and authenticate with the Elasticsearch cluster. In the next tutorial we will be modifying this example to fetch the secrets and certficate from azure key vault.
The spring boot project we will be developing is as follows -
Spring Boot 3 + Elasticsearch CRUD Example

Video

This tutorial is explained in the below Youtube Video.

Implementation

In a previous tutorial we had implemented an example to setup elasticsearch 8 with credentials. Go to the elasticsearch downloads page. Click on the Windows button to download the latest elasticsearch installable. In our case it is 8.8.2.
elasticsearch installable
This will be a zip folder. Unzip it as follows.
elasticsearch folder
Open the command prompt as an admin. Go to the elasticsearch bin folder and type the following command
elasticsearch.bat
This will start elasticsearch.
elasticsearch start
With elasticsearch 8 security is enabled by default. So https and password authentication is enabled by default. Using a browser if we now go to https://localhost:9200 we get the following prompt asking for a username and password.
elasticsearch 8 security
Next we will be setting the elasticsearch password. For this do not close the previous command prompt window which is running elasticsearch. Open another command prompt as as admin and go to the bin folder. We will be using the following command for setting the elasticsearch password.
elasticsearch-reset-password -u elastic --interactive

elasticsearch 8 security set password
I have set the elastic password as password. So if i now go to the elasticsearch url - localhost:9200 I can enter the username as elastic and password as password. elasticsearch.
elasticsearch 8 security tutorial
We are able to access the portal as follows -
elasticsearch 8 tutorial
We will be making use of Spring Initializr to create a spring boot project as follows -
Spring Boot 3 CRUD Elasticsearch Initializr
In the pom.xml we have the spring-boot-starter-elasticsearch-jpa dependency. This is used in a Spring Boot project to include the necessary dependencies to work with Java Persistence API (JPA) for data access and database operations. By including the jackson-databind dependency in your project, you ensure that the necessary JSON processing capabilities are available to the Elasticsearch Java client.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.5</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.javainuse</groupId>
	<artifactId>boot-elasticsearch-crud</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-elasticsearch-crud</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>




We will further need to add the following dependencies to our pom.xml. The co.elastic.clients:elasticsearch-java dependency is used to include the official Java client for Elasticsearch in our project. The Spring Data Elasticsearch project uses this client under the hood to communicate with the Elasticsearch cluster, enabling us to perform various operations such as indexing documents, executing searches, and managing indices. By including the jackson-databind dependency in your project, you ensure that the necessary JSON processing capabilities are available to the Elasticsearch Java client.
Spring Boot 3 CRUD Elasticsearch co.elastic.clients:elasticsearch-java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.5</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.javainuse</groupId>
	<artifactId>boot-elasticsearch-crud</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>boot-elasticsearch-crud</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>			
		</dependency>
		<dependency>
			<groupId>co.elastic.clients</groupId>
			<artifactId>elasticsearch-java</artifactId>
			<version>8.13.4</version>
		</dependency>	
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

By default elasticsearch runs on https. For this we will be create a truststore file. This truststore file is then used by your spring boot application to establish a secure SSL/TLS connection with the Elasticsearch cluster. In this truststore we will be importing the CA certificate of elasticsearch. When your application attempts to connect to Elasticsearch, it will use the CA certificate in the truststore to verify the identity of the Elasticsearch server and establish a trusted connection. For this go to the elasticsearch jdk/bin installation and run the following command
.\keytool.exe -import  -file "E:\trial\elasticsearch-8.13.4-windows-x86_64\elasticsearch-8.13.4\config\certs\http_ca.crt" -keystore "E:\trial\elasticsearch-8.13.4-windows-x86_64\elasticsearch-8.13.4\config\certs\truststore.p12" -storepass javainuse -noprompt -storetype pkcs12
The HttpClientConfigImpl class is a Spring configuration class that implements the HttpClientConfigCallback interface. This interface allows developers to customize the HttpAsyncClientBuilder, which is responsible for creating the client used to communicate with Elasticsearch. The HttpClientConfigImpl class performs the following tasks:
  • Authentication Setup: The class creates a CredentialsProvider object and sets the username and password credentials required for authentication with the Elasticsearch cluster. In the provided example, the username is "elastic" and the password is "javainuse".
  • SSL/TLS Configuration: To establish a secure connection with the Elasticsearch cluster, the class loads an SSL/TLS truststore from a specified file path (D:\elasticsearch-8.13.4-windows-x86\_64\elasticsearch-8.13.4\config\certs\truststore.p12). The truststore is a file that contains trusted Certificate Authorities (CAs) used for verifying the server's identity during the SSL/TLS handshake. The class creates an SSLContext object using the SSLContextBuilder and loads the truststore file into it, using the provided password ("password" in this case).
  • Client Configuration: Finally, the class sets the CredentialsProvider and SSLContext on the HttpAsyncClientBuilder. This builder is then used to create the Elasticsearch client, ensuring that the client can authenticate with the Elasticsearch cluster using the provided credentials and establish a secure connection using the specified truststore.
package com.javainuse.bootelastisearchcrud.config;

import java.io.File;

import javax.net.ssl.SSLContext;

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HttpClientConfigImpl implements HttpClientConfigCallback {

	@Override
	public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
		try {
			final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
			UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials("elastic",
					"javainuse");
			credentialsProvider.setCredentials(AuthScope.ANY, usernamePasswordCredentials);
			httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);

			String trustLocationStore = "E:\\trial\\elasticsearch-8.13.4-windows-x86_64\\elasticsearch-8.13.4\\config\\certs\\truststore.p12";
			File trustLocationFile = new File(trustLocationStore);

			SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(trustLocationFile,
					"javainuse".toCharArray());
			SSLContext sslContext = sslContextBuilder.build();
			httpClientBuilder.setSSLContext(sslContext);

		} catch (Exception e) {
		}
		return httpClientBuilder;
	}
}
Previously we discussed how to configure the Elasticsearch client to establish a secure and authenticated connection with the Elasticsearch cluster. However, to fully integrate Elasticsearch into our Spring Boot application, we need to create a bean that encapsulates the client configuration and provides an instance of the ElasticsearchClient class.
The ESClient class, annotated with @Component, serves this purpose. Let's break down the getElasticsearchClient method within this class:
  • Initializing the RestClientBuilder: The RestClientBuilder is initialized with an HttpHost object, which specifies the hostname, port, and protocol for connecting to the Elasticsearch cluster. In this example, the client is configured to connect to a local Elasticsearch instance running on http://localhost:9200.
  • Setting the HttpClientConfigCallback:
    The HttpClientConfigCallback implementation, HttpClientConfigImpl, is instantiated and set on the RestClientBuilder. This callback is responsible for configuring the HttpAsyncClientBuilder with the necessary credentials and SSL/TLS settings, as discussed in the previous section.
  • Building the RestClient:
    The RestClient instance is created using the configured RestClientBuilder.
  • Creating the RestClientTransport:
    The RestClientTransport is a wrapper around the RestClient that provides a low-level transport layer for communicating with the Elasticsearch cluster. It is initialized with the RestClient instance and a JacksonJsonpMapper for handling JSON serialization and deserialization.
  • Instantiating the ElasticsearchClient:
    Finally, an ElasticsearchClient instance is created using the RestClientTransport. This client provides a high-level API for interacting with the Elasticsearch cluster, allowing developers to perform various operations such as indexing documents, executing searches, and managing indices.
package com.javainuse.bootelastisearchcrud.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;

@Component
public class ESClient {

	@Bean
	public ElasticsearchClient getElasticsearchClient() {
		RestClientBuilder builder = RestClient.builder(new HttpHost("localhost", 9200, "https"));

		RestClientBuilder.HttpClientConfigCallback httpClientConfigCallback = new HttpClientConfigImpl();
		builder.setHttpClientConfigCallback(httpClientConfigCallback);

		RestClient restClient = builder.build();

		RestClientTransport restClientTransport = new RestClientTransport(restClient, new JacksonJsonpMapper());

		return new ElasticsearchClient(restClientTransport);
	}
}
Next we create an document class named Employee. This is the class which will map the employee to the elasticsearch index named employee.
In Elasticsearch, data is stored in the form of documents, which are organized into indices. When working with Elasticsearch from a Java application, it is often necessary to map Java objects to Elasticsearch documents and vice versa. Spring Data Elasticsearch provides annotations that simplify this mapping process.
package com.javainuse.bootelastisearchcrud.model;

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

@Document(indexName = "employee")
public class Employee {

	@Id
	private String id;
	
	private String name;

	private String department;
	
	public Employee() {
	}

	public Employee(String id, String name, String department) {
		super();
		this.id = id;
		this.name = name;
		this.department = department;
	}
	

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDepartment() {
		return department;
	}

	public void setDepartment(String department) {
		this.department = department;
	}
	
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", department=" + department + "]";
	}

}
Spring Data Elasticsearch provides a convenient way to interact with Elasticsearch by leveraging the repository pattern. By defining an interface that extends ElasticsearchRepository, Spring Data Elasticsearch automatically generates an implementation for common CRUD operations and custom queries on Elasticsearch documents.
By extending ElasticsearchRepository, the EmployeeRepository interface inherits a set of pre-defined methods for common CRUD operations on Employee documents in Elasticsearch. These methods include:
  • save(Entity): Saves a new entity or updates an existing one.
  • findById(ID): Retrieves an entity by its ID.
  • findAll(): Retrieves all entities.
  • deleteById(ID): Deletes an entity by its ID.
  • deleteAll(): Deletes all entities.
Create the EmployeeRepository class as follows -
package com.javainuse.springbootelasticsearchcrud.repository;

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import com.javainuse.springbootelasticsearchcrud.model.Employee;

public interface EmployeeRepository extends ElasticsearchRepository<Employee, String> {

}
If we now start the spring boot application and check the elasticsearch indices, an index named employee has been created. We can check this by using url - https://localhost:9200/_cat/indices?v
Spring Boot + Elasticsearch Create Index

Create Employee API


Spring Boot + Elasticsearch Create Operation

Employee DTO (Data Transfer Object)

Using DTO classes allows for a clear separation between the presentation layer and the domain layer. DTOs provide a way to transfer data between different layers, such as the controller and service layers, without leaking unnecessary information or domain-specific logic.
Create the EmployeeDto class as follows-
package com.javainuse.bootelastisearchcrud.dto;

public class EmployeeDto {

	private String id;
	private String name;
	private String department;

	public EmployeeDto(String id, String name, String department) {
		super();
		this.setId(id);
		this.name = name;
		this.department = department;
	}

	public EmployeeDto() {

	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDepartment() {
		return department;
	}

	public void setDepartment(String department) {
		this.department = department;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
}

Employee Mapper Implementation

In the service layer we need to convert EmployeeDto instance to Employee instance and viceversa. We perform this conversion operation using Employee Mapper.
package com.javainuse.bootelastisearchcrud.mapper;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.model.Employee;

public class EmployeeMapper {

	public static EmployeeDto mapToEmployeeDto(Employee employee) {
		return new EmployeeDto(employee.getId(), employee.getName(), employee.getDepartment());
	}

	public static Employee mapToEmployee(EmployeeDto employeeDto) {
		return new Employee(employeeDto.getId(), employeeDto.getName(), employeeDto.getDepartment());
	}

}

Service Implementation

We will now create the service layer. First let us create an interface named EmployeeService with single method named createEmployee which will create a new employee record. We are creating the service interface first as we want to follow the programming to an interface design principle.
package com.javainuse.bootelastisearchcrud.service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;

public interface EmployeeService {
	EmployeeDto createEmployee(EmployeeDto employeeDto);
}
Next we implement the service implementation named EmployeeSericeImpl. This class we autowire the repository. There is a method called createEmployee which takes an EmployeeDto object as a parameter. Inside this method, it maps the EmployeeDto object to an Employee object using a class called EmployeeMapper. Then, we save the employee object to the database using the employeeRepository.save() method. Finally, we map the created employee object back to an EmployeeDto object using EmployeeMapper and return it.
package com.javainuse.bootelastisearchcrud.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.mapper.EmployeeMapper;
import com.javainuse.bootelastisearchcrud.model.Employee;
import com.javainuse.bootelastisearchcrud.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Override
	public EmployeeDto createEmployee(EmployeeDto employeeDto) {
		Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
		Employee createdEmployee = employeeRepository.save(employee);
		return EmployeeMapper.mapToEmployeeDto(createdEmployee);
	}

}

Controller Implementation

EmployeeController class is a REST controller class that handles HTTP requests related to employee operations. It uses Spring's @RestController annotation to indicate that the class is a REST controller that facilitates the mapping of requests to methods.
The class has a dependency on the EmployeeService class, which is injected using the @Autowired annotation. This allows the class to utilize the methods provided by the EmployeeService to perform employee-related operations.
The class contains a single method, createEmployee, which is mapped to a POST request with the endpoint /employee. This method takes in an EmployeeDto object as the request body, representing the employee data to be created. It invokes the createEmployee method from the injected EmployeeService, passing the employeeDto as an argument. The response from the service method call is then wrapped in a ResponseEntity object, specifying the created employee DTO and the HTTP status code HttpStatus.CREATED as the response.
package com.javainuse.bootelastisearchcrud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.service.EmployeeService;

@RestController
public class EmployeeController {

	@Autowired
	private EmployeeService employeeService;

	@PostMapping(value = "/employee")
	public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employeeDto) {
		EmployeeDto createdEmployee = employeeService.createEmployee(employeeDto);
		return new ResponseEntity<>(createdEmployee, HttpStatus.CREATED);
	}
}

Test

Let us now test the create employee API.
Spring Boot + Elasticsearch create employee API
If we check elasticsearch
Spring Boot + Elasticsearch create record

Get Employee By Id API


Spring Boot + Elasticsearch Get by Id Operation

Service Implementation

Create an exception named EmployeeNotFoundException which will be thrown if the employee with the specified id is not found.
package com.javainuse.bootmysqlcrud.exception;

public class EmployeeNotFoundException extends Exception {

	private static final long serialVersionUID = 1L;

	public EmployeeNotFoundException() {
	}

	public EmployeeNotFoundException(String message) {
		super(message);
	}
}
In the EmployeeService interface, the following method has been added - EmployeeDto getEmployeeById(Long employeeId) throws EmployeeNotFoundException.
This method is used to retrieve information about an employee based on their unique identifier, which is provided as the employeeId parameter. The method returns an EmployeeDto object, which is a DTO (Data Transfer Object) containing the details of the employee. Additionally, this method throws an EmployeeNotFoundException if no employee with the provided ID is found. This exception is used to handle cases where the requested employee does not exist in the system or cannot be found. By throwing this exception, the calling code can handle and respond to such errors appropriately.
package com.javainuse.bootelastisearchcrud.service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;

public interface EmployeeService {
	EmployeeDto createEmployee(EmployeeDto employeeDto);

	EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException;
}
Next in the EmployeeServiceImpl we implement the getEmployeeById method of the EmployeeService interface. This method takes an employeeId as a parameter and returns an EmployeeDto object, which represents a Data Transfer Object (DTO) carrying employee information.
The method starts by using the employeeRepository, which is an instance of a Spring Data JPA repository, to retrieve an employee record from the database using the employeeId. The findById method returns an Optional<Employee> instance which may contain the employee object if it exists.
Next, the code checks if the employee object is empty or not using the isEmpty() method. If it is empty, it means no employee record was found with the given employeeId. In such a case, the code throws an EmployeeNotFoundException which is a custom exception class. The exception message includes the employeeId that was not found.
If the employee object is not empty, it means a valid employee record exists with the given employeeId. To convert the Employee object to an EmployeeDto object, the code uses a EmployeeMapper class that provides a static method called mapToEmployeeDto. The mapToEmployeeDto method takes an Employee object as input and returns an EmployeeDto object.
Finally, the method returns the Employee Dto object representing the employee found in the database.
package com.javainuse.bootelastisearchcrud.service;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.mapper.EmployeeMapper;
import com.javainuse.bootelastisearchcrud.model.Employee;
import com.javainuse.bootelastisearchcrud.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Override
	public EmployeeDto createEmployee(EmployeeDto employeeDto) {
		Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
		Employee createdEmployee = employeeRepository.save(employee);
		return EmployeeMapper.mapToEmployeeDto(createdEmployee);
	}

	@Override
	public EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException {
		Optional<Employee> employee = employeeRepository.findById(employeeId);
		if (employee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeId + " not found.");
		}
		return EmployeeMapper.mapToEmployeeDto(employee.get());
	}
}

Controller Implementation

Next in the controller class we define a GET request method that retrieves details of an employee by their employee ID. The @GetMapping annotation specifies that this method should handle HTTP GET requests with the specified URL "/employee/{employeeId}". The employeeId is a path variable and is extracted from the URL using the @PathVariable annotation. The method throws an EmployeeNotFoundException if the employee with the given ID is not found. Inside the method, it delegates the responsibility of retrieving the employee details to the employeeService which is an instance of the EmployeeService class. It calls the getEmployeeById method of the employeeService by passing the employeeId as a parameter. The result, which is an instance of EmployeeDto, is then returned as the response with a status code of OK (200) using the ResponseEntity class. If the requested employee is not found, it catches the EmployeeNotFoundException and re-throws it, allowing the exception to be handled by any exception handler defined in the application.
package com.javainuse.bootelastisearchcrud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.service.EmployeeService;

@RestController
public class EmployeeController {

	@Autowired
	private EmployeeService employeeService;

	@PostMapping(value = "/employee")
	public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employeeDto) {
		EmployeeDto createdEmployee = employeeService.createEmployee(employeeDto);
		return new ResponseEntity<>(createdEmployee, HttpStatus.CREATED);
	}

	@GetMapping(value = "/employee/{employeeId}")
	public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable("employeeId") String employeeId)
			throws EmployeeNotFoundException {
		try {
			EmployeeDto employee = employeeService.getEmployeeById(employeeId);
			return new ResponseEntity<>(employee, HttpStatus.OK);
		} catch (EmployeeNotFoundException employeeNotFoundException) {
			throw employeeNotFoundException;
		}
	}

}

Test

Let us now test the get employee by id API.
Spring Boot + Elasticsearch get employee by id API
If suppose we try to retrieve an employee that does not exist Let us now test the get employee by id API.
Spring Boot + Elasticsearch get employee resource not found exception

Get All Employees


Spring Boot + Elasticsearch Get All Employees Operation

Service Implementation

Next we define EmployeeService interface method named getEmployees(). This method returns a list of EmployeeDto objects.
package com.javainuse.bootelastisearchcrud.service;

import java.util.List;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;

public interface EmployeeService {
	EmployeeDto createEmployee(EmployeeDto employeeDto);

	EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException;

	List<EmployeeDto> getEmployees();
}
In the EmployeeServiceImpl, we define the getEmployees method that overrides a method with the same signature in the EmployeeService interface. It returns a list of EmployeeDto objects.
Inside the method, it uses the employeeRepository to retrieve a list of Employee objects from elasticsearch. Then, it uses the stream method on the list of Employee objects to create a stream, and applies a map operation to each element of the stream. The map operation takes a lambda expression that maps each Employee object to an EmployeeDto object using the EmployeeMapper.mapToEmployeeDto method.
Finally, it uses the collect operation to collect the mapped EmployeeDto objects into a list and returns that list.
package com.javainuse.bootelastisearchcrud.service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.mapper.EmployeeMapper;
import com.javainuse.bootelastisearchcrud.model.Employee;
import com.javainuse.bootelastisearchcrud.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Override
	public EmployeeDto createEmployee(EmployeeDto employeeDto) {
		Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
		Employee createdEmployee = employeeRepository.save(employee);
		return EmployeeMapper.mapToEmployeeDto(createdEmployee);
	}

	@Override
	public EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException {
		Optional<Employee> employee = employeeRepository.findById(employeeId);
		if (employee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeId + " not found.");
		}
		return EmployeeMapper.mapToEmployeeDto(employee.get());
	}

	@Override
	public List<EmployeeDto> getEmployees() {
		Iterable<Employee> employeesIterable = employeeRepository.findAll();
		List<Employee> employees = StreamSupport.stream(employeesIterable.spliterator(), false).toList();
		return employees.stream().map((emp) -> EmployeeMapper.mapToEmployeeDto(emp)).collect(Collectors.toList());
	}
}

Controller Implementation

Finally in the EmployeeController class we define the getEmployees method. @GetMapping(value = "/employees") is a mapping annotation that maps the method to the specific URL endpoint "/employees" and specifies that it will handle GET requests. The method implementation retrieves a list of employees using the employeeService.getEmployees() method, which is responsible for fetching the employee data from a data source such as a database. The retrieved list of employees is then wrapped in a ResponseEntity, which is an HTTP response object that allows us to control the HTTP response status and headers. In this case, the ResponseEntity is being created with the list of employees as the response body and HttpStatus.CREATED as the status code. Finally, the ResponseEntity object is returned from the method, indicating that the endpoint response will contain a list of employees.
package com.javainuse.bootelastisearchcrud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.service.EmployeeService;

@RestController
public class EmployeeController {

	@Autowired
	private EmployeeService employeeService;

	@PostMapping(value = "/employee")
	public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employeeDto) {
		EmployeeDto createdEmployee = employeeService.createEmployee(employeeDto);
		return new ResponseEntity<>(createdEmployee, HttpStatus.CREATED);
	}

	@GetMapping(value = "/employee/{employeeId}")
	public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable("employeeId") String employeeId)
			throws EmployeeNotFoundException {
		try {
			EmployeeDto employee = employeeService.getEmployeeById(employeeId);
			return new ResponseEntity<>(employee, HttpStatus.OK);
		} catch (EmployeeNotFoundException employeeNotFoundException) {
			throw employeeNotFoundException;
		}
	}

	@GetMapping(value = "/employees")
	public ResponseEntity<List<EmployeeDto>> getEmployees() {
		List<EmployeeDto> employees = employeeService.getEmployees();
		return new ResponseEntity<>(employees, HttpStatus.OK);
	}
}

Test

Let us now test the get all employees.
Spring Boot + Elasticsearch get employee by id API

Delete Employee By Id API


Spring Boot + Elasticsearch Delete By Id Operation

Service Implementation

The deleteEmployee method in the EmployeeService interface is used to delete an employee record from a database or any other persistence mechanism. It takes in the employeeId as a parameter which specifies the unique identifier for the employee. If the employee with the specified employeeId does not exist, it throws an EmployeeNotFoundException.
package com.javainuse.bootelastisearchcrud.service;

import java.util.List;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;

public interface EmployeeService {
	EmployeeDto createEmployee(EmployeeDto employeeDto);

	EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException;

	List<EmployeeDto> getEmployees();

	void deleteEmployee(String employeeId) throws EmployeeNotFoundException;
}
The deleteEmployee method deletes an employee from the employee repository based on their ID.
The method takes in a "Long" parameter called "employeeId," representing the ID of the employee to be deleted.
It uses the "employeeRepository" to find the employee with the given ID by calling the "findById" method. This method returns an "Optional<Employee>," which may or may not contain a value.
Next, it checks if the "employee" optional is empty using the "isEmpty" method. If it's empty, it means that the employee with the given ID does not exist in the repository. In this case, the method throws an "EmployeeNotFoundException" with a specific message mentioning the employee ID.
If the employee exists, the method proceeds to delete the employee from the repository by calling the "deleteById" method on the "employeeRepository" with the employeeId parameter.
package com.javainuse.bootelastisearchcrud.service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.mapper.EmployeeMapper;
import com.javainuse.bootelastisearchcrud.model.Employee;
import com.javainuse.bootelastisearchcrud.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Override
	public EmployeeDto createEmployee(EmployeeDto employeeDto) {
		Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
		Employee createdEmployee = employeeRepository.save(employee);
		return EmployeeMapper.mapToEmployeeDto(createdEmployee);
	}

	@Override
	public EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException {
		Optional<Employee> employee = employeeRepository.findById(employeeId);
		if (employee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeId + " not found.");
		}
		return EmployeeMapper.mapToEmployeeDto(employee.get());
	}

	@Override
	public List<EmployeeDto> getEmployees() {
		Iterable<Employee> employeesIterable = employeeRepository.findAll();
		List<Employee> employees = StreamSupport.stream(employeesIterable.spliterator(), false).toList();
		return employees.stream().map((emp) -> EmployeeMapper.mapToEmployeeDto(emp)).collect(Collectors.toList());
	}

	@Override
	public void deleteEmployee(String employeeId) throws EmployeeNotFoundException {
		Optional<Employee> employee = employeeRepository.findById(employeeId);
		if (employee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeId + " not found.");
		}
		employeeRepository.deleteById(employeeId);
	}

}

Controller Implementation

The deleteEmployees method is a DELETE request mapping method that deletes an employee with the specified employeeId. It throws an EmployeeNotFoundException if the employee with the specified id is not found. After deleting the employee, it returns a ResponseEntity with a status of HttpStatus.NO_CONTENT.
package com.javainuse.bootelastisearchcrud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.service.EmployeeService;

@RestController
public class EmployeeController {

	@Autowired
	private EmployeeService employeeService;

	@PostMapping(value = "/employee")
	public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employeeDto) {
		EmployeeDto createdEmployee = employeeService.createEmployee(employeeDto);
		return new ResponseEntity<>(createdEmployee, HttpStatus.CREATED);
	}

	@GetMapping(value = "/employee/{employeeId}")
	public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable("employeeId") String employeeId)
			throws EmployeeNotFoundException {
		try {
			EmployeeDto employee = employeeService.getEmployeeById(employeeId);
			return new ResponseEntity<>(employee, HttpStatus.OK);
		} catch (EmployeeNotFoundException employeeNotFoundException) {
			throw employeeNotFoundException;
		}
	}

	@GetMapping(value = "/employees")
	public ResponseEntity<List<EmployeeDto>> getEmployees() {
		List<EmployeeDto> employees = employeeService.getEmployees();
		return new ResponseEntity<>(employees, HttpStatus.OK);
	}

	@DeleteMapping(value = "/employee/{employeeId}")
	public ResponseEntity<HttpStatus> deleteEmployees(@PathVariable("employeeId") String employeeId)
			throws EmployeeNotFoundException {
		employeeService.deleteEmployee(employeeId);
		return new ResponseEntity<>(HttpStatus.NO_CONTENT);
	}

}

Test

Let us now test the delete employee by id api.
Spring Boot + Elasticsearch delete employee by id API

Update Employee API


Spring Boot + Elasticsearch Update Operation

Service Implementation

The updateEmployee method in the EmployeeService interface is used to update the details of an employee. It takes an EmployeeDto object as a parameter, which contains the updated information for the employee. The method returns the updated EmployeeDto object after the update is successful.
If the employee with the specified employeeDto does not exist in the system, the method throws an EmployeeNotFoundException, indicating that the employee could not be updated because they were not found.
package com.javainuse.bootelastisearchcrud.service;

import java.util.List;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;

public interface EmployeeService {
	EmployeeDto createEmployee(EmployeeDto employeeDto);

	EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException;

	List<EmployeeDto> getEmployees();

	void deleteEmployee(String employeeId) throws EmployeeNotFoundException;

	EmployeeDto updateEmployee(EmployeeDto employeeDto) throws EmployeeNotFoundException;
}
The method updateEmployee in the EmployeeServiceImpl class updates an employee record in the database based on the provided EmployeeDto object.

It first retrieves the employee record from the database using the findById method of the employeeRepository object.
If the retrieved employee is empty (not found), it throws an EmployeeNotFoundException with a message indicating that the employee with the given ID was not found.
If the employee is found, it updates the employee object with the values from the employeeDto object by calling the setName and setDepartment methods on the employee object.
It then saves the updated employee object to the database using the save method of the employeeRepository object.
Finally, it returns a mapped EmployeeDto object based on the saved employee object using the mapToEmployeeDto method of the EmployeeMapper class.
package com.javainuse.bootelastisearchcrud.service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.mapper.EmployeeMapper;
import com.javainuse.bootelastisearchcrud.model.Employee;
import com.javainuse.bootelastisearchcrud.repository.EmployeeRepository;

@Service
public class EmployeeServiceImpl implements EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Override
	public EmployeeDto createEmployee(EmployeeDto employeeDto) {
		Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
		Employee createdEmployee = employeeRepository.save(employee);
		return EmployeeMapper.mapToEmployeeDto(createdEmployee);
	}

	@Override
	public EmployeeDto getEmployeeById(String employeeId) throws EmployeeNotFoundException {
		Optional<Employee> employee = employeeRepository.findById(employeeId);
		if (employee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeId + " not found.");
		}
		return EmployeeMapper.mapToEmployeeDto(employee.get());
	}

	@Override
	public List<EmployeeDto> getEmployees() {
		Iterable<Employee> employeesIterable = employeeRepository.findAll();
		List<Employee> employees = StreamSupport.stream(employeesIterable.spliterator(), false).toList();
		return employees.stream().map((emp) -> EmployeeMapper.mapToEmployeeDto(emp)).collect(Collectors.toList());
	}

	@Override
	public void deleteEmployee(String employeeId) throws EmployeeNotFoundException {
		Optional<Employee> employee = employeeRepository.findById(employeeId);
		if (employee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeId + " not found.");
		}
		employeeRepository.deleteById(employeeId);
	}

	@Override
	public EmployeeDto updateEmployee(EmployeeDto employeeDto) throws EmployeeNotFoundException {
		Optional<Employee> retrievedEmployee = employeeRepository.findById(employeeDto.getId());
		if (retrievedEmployee.isEmpty()) {
			throw new EmployeeNotFoundException("Employee with id - " + employeeDto.getId() + " not found.");
		}
		Employee employee = retrievedEmployee.get();
		employee.setName(employeeDto.getName());
		employee.setDepartment(employeeDto.getDepartment());
		Employee createdEmployee = employeeRepository.save(employee);
		return EmployeeMapper.mapToEmployeeDto(createdEmployee);
	}

}

Controller Implementation

The method updateEmployee in EmployeeController is responsible for updating an employee's information. It takes an EmployeeDto object as a parameter, which contains the updated information for the employee.
Inside the method, it calls the updateEmployee method of the employeeService to update the employee's information. The updated EmployeeDto object is then returned.
Finally, the method creates a new ResponseEntity object using the updated EmployeeDto and specifies the HTTP status code as HttpStatus.OK. This ResponseEntity object is returned as the response.
package com.javainuse.bootelastisearchcrud.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.bootelastisearchcrud.dto.EmployeeDto;
import com.javainuse.bootelastisearchcrud.exception.EmployeeNotFoundException;
import com.javainuse.bootelastisearchcrud.service.EmployeeService;

@RestController
public class EmployeeController {

	@Autowired
	private EmployeeService employeeService;

	@PostMapping(value = "/employee")
	public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employeeDto) {
		EmployeeDto createdEmployee = employeeService.createEmployee(employeeDto);
		return new ResponseEntity<>(createdEmployee, HttpStatus.CREATED);
	}

	@GetMapping(value = "/employee/{employeeId}")
	public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable("employeeId") String employeeId)
			throws EmployeeNotFoundException {
		try {
			EmployeeDto employee = employeeService.getEmployeeById(employeeId);
			return new ResponseEntity<>(employee, HttpStatus.OK);
		} catch (EmployeeNotFoundException employeeNotFoundException) {
			throw employeeNotFoundException;
		}
	}

	@GetMapping(value = "/employees")
	public ResponseEntity<List<EmployeeDto>> getEmployees() {
		List<EmployeeDto> employees = employeeService.getEmployees();
		return new ResponseEntity<>(employees, HttpStatus.OK);
	}

	@DeleteMapping(value = "/employee/{employeeId}")
	public ResponseEntity<HttpStatus> deleteEmployees(@PathVariable("employeeId") String employeeId)
			throws EmployeeNotFoundException {
		employeeService.deleteEmployee(employeeId);
		return new ResponseEntity<>(HttpStatus.NO_CONTENT);
	}

	@PutMapping(value = "/employee")
	public ResponseEntity<EmployeeDto> updateEmployee(@RequestBody EmployeeDto employeeDto)
			throws EmployeeNotFoundException {
		EmployeeDto createdEmployee = employeeService.updateEmployee(employeeDto);
		return new ResponseEntity<>(createdEmployee, HttpStatus.OK);
	}

}

Test

Let us now test the update employee by id.
Spring Boot + Elasticsearch update employee by id API

Download Source Code

Download it -
Spring Boot 3 + Elasticsearch CRUD Example