Building Google Style Search Autocomplete System - Part 2 Spring Boot Backend with Elasticsearch and Redis

Learn how to build the backend search service for a Google-style autocomplete system using Spring Boot, PostgreSQL, Elasticsearch indexing and Redis caching.

Creating Spring Boot Search Service

In this part, we will build the core backend service responsible for processing search requests. The service will read keywords from PostgreSQL, create Elasticsearch indexes, use Redis caching, and provide autocomplete APIs for the React frontend.

Backend Architecture

React Client

      |
      |

Spring Boot Search API

      |
      |-------------------
      |                   |
   Redis Cache      Elasticsearch
      |
      |
 PostgreSQL Database

Create Spring Boot Project

Create a Spring Boot project using Spring Initializr with Java 21 and Maven.

  • Spring Web
  • Spring Data JPA
  • Spring Data Elasticsearch
  • Spring Data Redis
  • PostgreSQL Driver
  • Lombok

Backend Folder Structure

search-service

src/main/java/com/example/search

├── controller
│    └── SearchController.java
├── service
│    ├── SearchService.java
│    └── ElasticIndexService.java
├── entity
│    └── Keyword.java
├── document
│    └── KeywordDocument.java
├── repository
│    ├── KeywordRepository.java
│    └── KeywordElasticRepository.java
└── config
     ├── RedisConfig.java
     └── CorsConfig.java

application.yml Configuration

server:
  port: 8080

spring:

 datasource:
  url: jdbc:postgresql://localhost:5432/searchdb
  username: admin
  password: admin123

 jpa:
  hibernate:
   ddl-auto: update

 data:
  redis:
   host: localhost
   port: 6379

 elasticsearch:
  uris: http://localhost:9200

Create Keyword Entity

PostgreSQL stores the master keyword data. Elasticsearch will later create a searchable index from this data.

package com.example.search.entity;

import jakarta.persistence.*;
import lombok.Data;

@Entity
@Data
@Table(name="keywords")
public class Keyword {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 private String keyword;

 private Long popularity;

}

Create Repository Layer

package com.example.search.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.search.entity.Keyword;

public interface KeywordRepository
extends JpaRepository<Keyword,Long>{

}

Insert Sample Data

INSERT INTO keywords(keyword,popularity)
VALUES
('spring boot',90000),
('spring security',80000),
('spring cloud',70000),
('java tutorial',60000),
('react hooks',50000);

Create Elasticsearch Document

Elasticsearch does not directly search PostgreSQL. We create a separate search document optimized for autocomplete.

package com.example.search.document;

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

@Data
@Document(indexName="keyword_index")
public class KeywordDocument {

@Id
private String id;

private String keyword;

private Long popularity;

}

Elasticsearch Repository

package com.example.search.repository;

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.example.search.document.KeywordDocument;

public interface KeywordElasticRepository
extends ElasticsearchRepository<KeywordDocument,String>{

}

Sync PostgreSQL Data to Elasticsearch

When the application starts, keyword data is copied from PostgreSQL into Elasticsearch.

@Service
public class ElasticIndexService {

private final KeywordRepository keywordRepository;
private final KeywordElasticRepository elasticRepository;

public void sync(){

keywordRepository.findAll()
.stream()
.map(k -> {

KeywordDocument doc=new KeywordDocument();
doc.setId(String.valueOf(k.getId()));
doc.setKeyword(k.getKeyword());
doc.setPopularity(k.getPopularity());

return doc;

})
.forEach(elasticRepository::save);

}
}

Create Search Service

@Service
public class SearchService {

private final KeywordElasticRepository repository;

public List<KeywordDocument> search(String text){

return repository
.findByKeywordContainingIgnoreCase(text);

}

}

Add Elasticsearch Search Method

List<KeywordDocument> findByKeywordContainingIgnoreCase(String keyword);

Create Search Controller

@RestController
@RequestMapping("/api/search")
public class SearchController {

private final SearchService service;

@GetMapping
public List<KeywordDocument> search(
@RequestParam String q
){

return service.search(q);

}

}

Add Redis Cache

Frequently searched terms should not hit Elasticsearch every time. Redis stores previous search responses.

@Service
public class SearchService {

private final RedisTemplate<String,Object> redis;

public Object search(String keyword){

String key="search:"+keyword;

Object cached=redis.opsForValue().get(key);

if(cached!=null){
return cached;
}

Object result=databaseSearch(keyword);

redis.opsForValue()
.set(key,result,Duration.ofMinutes(10));

return result;

}
}

Testing Search API

GET http://localhost:8080/api/search?q=spr

Expected Response

[
 {
  "keyword":"spring boot",
  "popularity":90000
 },
 {
  "keyword":"spring security",
  "popularity":80000
 }
]

Backend Development Flow Completed

  • Spring Boot REST API created
  • PostgreSQL keyword storage created
  • Elasticsearch indexing implemented
  • Redis caching added
  • Autocomplete API ready for React

Next Part

In the next part, we will build the analytics microservice using Kafka, implement search tracking, trending searches, popularity ranking and event-driven architecture.