diff --git a/pom.xml b/pom.xml
index 84eb1596..27341f1e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
com.iemr.common.identity
identity-api
- 3.6.0
+ 3.6.1
war
@@ -73,6 +73,26 @@
1.3.2
+ org.springframework.boot
+ spring-boot-starter-data-elasticsearch
+
+
+ co.elastic.clients
+ elasticsearch-java
+ 8.11.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-elasticsearch
+
+
+ co.elastic.clients
+ elasticsearch-java
+ 8.11.0
+
+
+
org.springframework.boot
spring-boot-devtools
runtime
diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties
index 75ad8759..98736180 100644
--- a/src/main/environment/common_ci.properties
+++ b/src/main/environment/common_ci.properties
@@ -23,3 +23,13 @@ fhir-url=@env.FHIR_API@
spring.redis.host=@env.REDIS_HOST@
cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@
+
+# Elasticsearch Configuration
+elasticsearch.host=@env.ELASTICSEARCH_HOST@
+elasticsearch.port=@env.ELASTICSEARCH_PORT@
+elasticsearch.username=@env.ELASTICSEARCH_USERNAME@
+elasticsearch.password=@env.ELASTICSEARCH_PASSWORD@
+elasticsearch.index.beneficiary=@env.ELASTICSEARCH_INDEX_BENEFICIARY@
+
+# Enable/Disable ES (for gradual rollout)
+elasticsearch.enabled=@env.ELASTICSEARCH_ENABLED@
diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties
index 90942c28..8f66c5b0 100644
--- a/src/main/environment/common_docker.properties
+++ b/src/main/environment/common_docker.properties
@@ -23,3 +23,13 @@ fhir-url=${FHIR_API}
spring.redis.host=${REDIS_HOST}
cors.allowed-origins=${CORS_ALLOWED_ORIGINS}
+
+# Elasticsearch Configuration
+elasticsearch.host=${ELASTICSEARCH_HOST}
+elasticsearch.port=${ELASTICSEARCH_PORT}
+elasticsearch.username=${ELASTICSEARCH_USERNAME}
+elasticsearch.password=${ELASTICSEARCH_PASSWORD}
+elasticsearch.index.beneficiary=${ELASTICSEARCH_INDEX_BENEFICIARY}
+
+# Enable/Disable ES (for gradual rollout)
+elasticsearch.enabled=${ELASTICSEARCH_ENABLED}
\ No newline at end of file
diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties
index ad15db00..45018aa6 100644
--- a/src/main/environment/common_example.properties
+++ b/src/main/environment/common_example.properties
@@ -18,3 +18,14 @@ fhir-url=http://localhost:8093/
# Redis Config
spring.redis.host=localhost
cors.allowed-origins=http://localhost:*
+
+# Elasticsearch Configuration
+elasticsearch.host=localhost
+elasticsearch.port=9200
+elasticsearch.username=elastic
+elasticsearch.password=piramalES
+elasticsearch.index.beneficiary=beneficiary_index
+
+# Enable/Disable ES (for gradual rollout)
+elasticsearch.enabled=true
+
diff --git a/src/main/java/com/iemr/common/identity/ScheduledSyncJob.java b/src/main/java/com/iemr/common/identity/ScheduledSyncJob.java
new file mode 100644
index 00000000..568d072c
--- /dev/null
+++ b/src/main/java/com/iemr/common/identity/ScheduledSyncJob.java
@@ -0,0 +1,70 @@
+package com.iemr.common.identity;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import com.iemr.common.identity.data.elasticsearch.ElasticsearchSyncJob;
+import com.iemr.common.identity.service.elasticsearch.SyncJobService;
+
+/**
+ * Scheduled jobs for Elasticsearch sync
+ *
+ * To enable scheduled sync, set: elasticsearch.sync.scheduled.enabled=true in
+ * application.properties
+ */
+@Component
+public class ScheduledSyncJob {
+
+ private static final Logger logger = LoggerFactory.getLogger(ScheduledSyncJob.class);
+
+ @Autowired
+ private SyncJobService syncJobService;
+
+ @Value("${elasticsearch.sync.scheduled.enabled:true}")
+ private boolean scheduledSyncEnabled;
+
+ /**
+ * Run full sync every day at 2 AM Cron: second, minute, hour, day, month,
+ * weekday
+ */
+ @Scheduled(cron = "${elasticsearch.sync.scheduled.cron:0 0 2 * * ?}")
+ public void scheduledFullSync() {
+ if (!scheduledSyncEnabled) {
+ logger.debug("Scheduled sync is disabled");
+ return;
+ }
+
+ logger.info("Starting scheduled full sync job");
+ try {
+ // Check if there's already a sync running
+ if (syncJobService.isFullSyncRunning()) {
+ logger.warn("Full sync already running. Skipping scheduled sync.");
+ return;
+ }
+
+ // Start async sync
+ ElasticsearchSyncJob job = syncJobService.startFullSyncJob("SCHEDULER");
+ logger.info("Scheduled sync job started: jobId={}", job.getJobId());
+
+ } catch (Exception e) {
+ logger.error("Error starting scheduled sync: {}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Clean up old completed jobs (keep last 30 days) Runs every Sunday at 3 AM
+ */
+ @Scheduled(cron = "0 0 3 * * SUN")
+ public void cleanupOldJobs() {
+ if (!scheduledSyncEnabled) {
+ return;
+ }
+
+ logger.info("Running cleanup of old sync jobs...");
+
+ }
+}
diff --git a/src/main/java/com/iemr/common/identity/config/ElasticsearchConfig.java b/src/main/java/com/iemr/common/identity/config/ElasticsearchConfig.java
new file mode 100644
index 00000000..a908b646
--- /dev/null
+++ b/src/main/java/com/iemr/common/identity/config/ElasticsearchConfig.java
@@ -0,0 +1,100 @@
+package com.iemr.common.identity.config;
+
+import java.io.IOException;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.json.jackson.JacksonJsonpMapper;
+import co.elastic.clients.transport.ElasticsearchTransport;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.elasticsearch.client.RestClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ElasticsearchConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(ElasticsearchConfig.class);
+
+ @Value("${elasticsearch.host}")
+ private String esHost;
+
+ @Value("${elasticsearch.port}")
+ private int esPort;
+
+ @Value("${elasticsearch.username}")
+ private String esUsername;
+
+ @Value("${elasticsearch.password}")
+ private String esPassword;
+
+ @Value("${elasticsearch.index.beneficiary}")
+ private String indexName;
+
+ @Bean
+ public ElasticsearchClient elasticsearchClient() {
+ BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(
+ AuthScope.ANY,
+ new UsernamePasswordCredentials(esUsername, esPassword)
+ );
+
+ RestClient restClient = RestClient.builder(
+ new HttpHost(esHost, esPort, "http")
+ ).setHttpClientConfigCallback(httpClientBuilder ->
+ httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
+ ).build();
+
+ ElasticsearchTransport transport = new RestClientTransport(
+ restClient,
+ new JacksonJsonpMapper()
+ );
+
+ return new ElasticsearchClient(transport);
+ }
+
+ @Bean
+ public Boolean createIndexMapping(ElasticsearchClient client) throws IOException {
+
+ // Check if index exists
+ boolean exists = client.indices().exists(e -> e.index(indexName)).value();
+
+ if (!exists) {
+ client.indices().create(c -> c
+ .index(indexName)
+ .mappings(m -> m
+ .properties("beneficiaryRegID", p -> p.keyword(k -> k))
+ .properties("firstName", p -> p.text(t -> t
+ .fields("keyword", f -> f.keyword(k -> k))
+ .analyzer("standard")
+ ))
+ .properties("lastName", p -> p.text(t -> t
+ .fields("keyword", f -> f.keyword(k -> k))
+ .analyzer("standard")
+ ))
+ .properties("phoneNum", p -> p.keyword(k -> k))
+ .properties("fatherName", p -> p.text(t -> t.analyzer("standard")))
+ .properties("spouseName", p -> p.text(t -> t.analyzer("standard")))
+ .properties("aadharNo", p -> p.keyword(k -> k))
+ .properties("govtIdentityNo", p -> p.keyword(k -> k))
+ )
+ .settings(s -> s
+ .numberOfShards("3")
+ .numberOfReplicas("1")
+ .refreshInterval(t -> t.time("1s"))
+ )
+ );
+
+ logger.info("Created Elasticsearch index with proper mappings");
+ }
+ return true;
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iemr/common/identity/config/ElasticsearchSyncConfig.java b/src/main/java/com/iemr/common/identity/config/ElasticsearchSyncConfig.java
new file mode 100644
index 00000000..bb72f91a
--- /dev/null
+++ b/src/main/java/com/iemr/common/identity/config/ElasticsearchSyncConfig.java
@@ -0,0 +1,56 @@
+package com.iemr.common.identity.config;
+
+import java.util.concurrent.Executor;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+/**
+ * Configuration for async processing and scheduling
+ */
+@Configuration
+@EnableAsync
+@EnableScheduling
+public class ElasticsearchSyncConfig {
+
+ /**
+ * Thread pool for Elasticsearch sync operations
+ * Configured for long-running background jobs
+ */
+ @Bean(name = "elasticsearchSyncExecutor")
+ public Executor elasticsearchSyncExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+ // Only 1-2 sync jobs should run at a time to avoid overwhelming DB/ES
+ executor.setCorePoolSize(5);
+ executor.setMaxPoolSize(10);
+ executor.setQueueCapacity(100);
+ executor.setThreadNamePrefix("es-sync-");
+ executor.setKeepAliveSeconds(60);
+
+ // Handle rejected tasks
+ executor.setRejectedExecutionHandler((r, executor1) -> {
+ throw new RuntimeException("Elasticsearch sync queue is full. Please wait for current job to complete.");
+ });
+
+ executor.initialize();
+ return executor;
+ }
+
+ /**
+ * General purpose async executor
+ */
+ @Bean(name = "taskExecutor")
+ public Executor taskExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(5);
+ executor.setMaxPoolSize(10);
+ executor.setQueueCapacity(100);
+ executor.setThreadNamePrefix("async-");
+ executor.initialize();
+ return executor;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityESController.java b/src/main/java/com/iemr/common/identity/controller/IdentityESController.java
new file mode 100644
index 00000000..4e4fbedf
--- /dev/null
+++ b/src/main/java/com/iemr/common/identity/controller/IdentityESController.java
@@ -0,0 +1,69 @@
+package com.iemr.common.identity.controller;
+
+
+import java.util.HashMap;
+import java.util.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import com.iemr.common.identity.service.elasticsearch.ElasticsearchService;
+import com.iemr.common.identity.utils.CookieUtil;
+import com.iemr.common.identity.utils.JwtUtil;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * Elasticsearch-enabled Beneficiary Search Controller
+ * All search endpoints with ES support
+ */
+@RestController
+@RequestMapping("/beneficiary")
+public class IdentityESController {
+
+ private static final Logger logger = LoggerFactory.getLogger(IdentityESController.class);
+
+ @Autowired
+ private ElasticsearchService elasticsearchService;
+
+ @Autowired
+ private JwtUtil jwtUtil;
+
+ /**
+ * MAIN UNIVERSAL SEARCH ENDPOINT
+ * Searches across all fields - name, phone, ID, etc.
+ *
+ * Usage: GET /beneficiary/search?query=vani
+ * Usage: GET /beneficiary/search?query=9876543210
+ */
+ @GetMapping("/search")
+ public ResponseEntity