Skip to content

feat(health,version): add health and version endpoints#136

Merged
drtechie merged 20 commits intoPSMRI:mainfrom
DurgaPrasad-54:feat/health-version
Mar 2, 2026
Merged

feat(health,version): add health and version endpoints#136
drtechie merged 20 commits intoPSMRI:mainfrom
DurgaPrasad-54:feat/health-version

Conversation

@DurgaPrasad-54
Copy link
Contributor

@DurgaPrasad-54 DurgaPrasad-54 commented Feb 17, 2026

📋 Description

JIRA ID:

This PR implements /health and /version endpoints in the Identity-API as part of Issue PSMRI/AMRIT#102

What’s changed

  • Add /health endpoint
  • Add /version endpoint

Summary by CodeRabbit

  • New Features

    • Added a /health endpoint returning aggregated component statuses (DB, cache, optional search), timestamps, and HTTP 200/503 responses.
    • Version endpoint now returns structured JSON with repository/build metadata.
  • Accessibility

    • /health and /version are publicly accessible without authentication.
  • Improvements

    • JWT filter now skips validation for public endpoints and logging uses placeholder-based messages.
  • Chores

    • Build configuration updated and an Elasticsearch client added to support health checks.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d130a85 and e9c7c33.

📒 Files selected for processing (2)
  • src/main/java/com/iemr/common/identity/controller/health/HealthController.java
  • src/main/java/com/iemr/common/identity/service/health/HealthService.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/iemr/common/identity/controller/health/HealthController.java
  • src/main/java/com/iemr/common/identity/service/health/HealthService.java

📝 Walkthrough

Walkthrough

Adds a /health REST endpoint and HealthService performing concurrent checks for MySQL, Redis, and optional Elasticsearch; refactors /version to return JSON; updates Maven git-commit-id plugin and adds Elasticsearch REST client dependency; JWT filter now skips validation for public /health and /version endpoints.

Changes

Cohort / File(s) Summary
Build Configuration
pom.xml
Replaced pl.project13.maven:git-commit-id-plugin:4.9.10 with io.github.git-commit-id:git-commit-id-maven-plugin:9.0.2 (execution moved to initialize, removed legacy options, added includeOnlyProperties, failOnNoGitDirectory, failOnUnableToExtractRepoInfo). Added runtime dependency org.elasticsearch.client:elasticsearch-rest-client:8.10.0.
Health Feature
src/main/java/com/iemr/common/identity/controller/health/HealthController.java, src/main/java/com/iemr/common/identity/service/health/HealthService.java
Added HealthController exposing GET /health and HealthService performing concurrent health checks for MySQL, Redis, and optional Elasticsearch; aggregates per-component statuses, timings, details, timestamps, and manages ES client lifecycle and caching.
Version Endpoint Refactor
src/main/java/com/iemr/common/identity/controller/version/VersionController.java
Changed versionInformation() to return ResponseEntity<Map<String,String>>, loads git.properties into a map with fallbacks, improved error handling, and returns JSON.
JWT Filter Enhancement
src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java
Added early bypass for public endpoints (/health, /version) and converted string-concatenation logs to parameterized logging placeholders.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as HealthController
    participant Auth as JwtAuthenticationUtil
    participant Service as HealthService
    participant MySQL as MySQL
    participant Redis as Redis
    participant ES as Elasticsearch

    Client->>Controller: GET /health (token)
    Controller->>Auth: validateToken(token)
    alt token valid
        Auth-->>Controller: valid
    else token invalid/absent
        Auth-->>Controller: invalid/absent
    end
    Controller->>Service: checkHealth()
    par MySQL check
        Service->>MySQL: SELECT 1 / health query
        MySQL-->>Service: result
    and Redis check
        Service->>Redis: PING (with timeout)
        Redis-->>Service: PONG / error
    and Elasticsearch check
        Service->>ES: /_cluster/health and optional probes (if enabled)
        ES-->>Service: cluster status / probe result
    end
    Service-->>Controller: aggregated health map
    alt overall UP or DEGRADED
        Controller-->>Client: HTTP 200 + details
    else overall DOWN
        Controller-->>Client: HTTP 503 + details
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through hosts with tiny paws,

Pinged the DB and checked the claws,
Redis hummed and ES replied,
Tokens glanced and logs kept tidy,
Now endpoints nap — the rabbit's pleased.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: adding health and version endpoints, which aligns with the changeset containing HealthController, HealthService, VersionController updates, and supporting infrastructure changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
src/main/java/com/iemr/common/identity/controller/health/HealthController.java (1)

94-127: Token extraction logic is duplicated from JwtUserIdValidationFilter.

isUserAuthenticated re-implements JWT extraction from headers and cookies, mirroring logic already present in the filter. Consider extracting a shared utility method (e.g., in JwtAuthenticationUtil or a new helper) to avoid maintaining the same extraction logic in two places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/iemr/common/identity/controller/health/HealthController.java`
around lines 94 - 127, The isUserAuthenticated method duplicates JWT extraction
logic from JwtUserIdValidationFilter; refactor by adding a shared extractor
(e.g., JwtAuthenticationUtil.extractTokenFromRequest(HttpServletRequest)) that
encapsulates reading JwtToken header, Authorization Bearer header, and cookies
(preserving current precedence), then update
HealthController.isUserAuthenticated to call that extractor and then
jwtAuthenticationUtil.validateUserIdAndJwtToken(token); also modify
JwtUserIdValidationFilter to use the same new extractor so token parsing logic
is maintained in one place.
src/main/java/com/iemr/common/identity/service/health/HealthService.java (2)

121-154: Health checks run sequentially — consider parallel execution for lower latency.

MySQL, Redis, and Elasticsearch checks are independent but invoked sequentially. If any one blocks for its full timeout (2-3 s), the total latency stacks up. Since you already have an ExecutorService, you could run them concurrently with CompletableFuture.allOf to keep response time bounded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 121 - 154, The health checks in checkHealth currently run
sequentially; modify checkHealth to run checkMySQLHealth, checkRedisHealth (only
if redisTemplate!=null) and checkElasticsearchHealth (only if
elasticsearchEnabled && elasticsearchClientReady) concurrently using
CompletableFuture.supplyAsync with your existing ExecutorService and combine
them with CompletableFuture.allOf, then gather each result future (handling
exceptions by returning a failing component map) and populate components and
overallHealth using isHealthy; ensure timestamp, STATUS_KEY, logging and
includeDetails behavior remain the same and that exceptions are logged and
translated into component DOWN entries so a hung call doesn't block the whole
check.

362-416: JDBC URL parsers are fragile — consider java.net.URI or a regex.

The manual indexOf/substring parsing only handles the jdbc:mysql://host:port/db format. URLs with credentials (user:pass@host), IPv6 addresses, or multiple hosts (failover URLs) will be mis-parsed. Using URI.create(jdbcUrl.substring(5)) (stripping the jdbc: prefix) would be more robust.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 362 - 416, The current manual parsing in extractHost, extractPort,
and extractDatabaseName is fragile; replace it by stripping the "jdbc:" prefix
and using java.net.URI to parse the remaining URI (or URI.create) so you
correctly handle credentials, IPv6, and multi-host forms: in extractHost call
new URI(withoutJdbc).getHost() and return UNKNOWN_VALUE if null, in extractPort
call new URI(...).getPort() and return "3306" when getPort() returns -1, and in
extractDatabaseName use new URI(...).getPath() (trim leading '/') and strip any
query component; wrap URI creation in try/catch and log debug on failure
preserving existing fallback values so behavior remains safe for malformed
inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pom.xml`:
- Around line 295-317: Update the git-commit-id-maven-plugin version: in the
plugin block for io.github.git-commit-id:git-commit-id-maven-plugin (the
execution id "get-the-git-infos" and goal "revision"), change the <version> from
7.0.0 to the latest stable release (e.g. 9.0.2) so the plugin benefits from
recent fixes and improvements while keeping the existing configuration elements
like includeOnlyProperties and failOnNoGitDirectory.
- Around line 248-253: The dependency entry for
org.elasticsearch.client:elasticsearch-rest-client is pinned to 8.10.0; update
the <version> to 9.3.0 in the pom and run a full build to surface any API or
transitive dependency changes, then evaluate and migrate usage from the legacy
artifactId (elasticsearch-rest-client) to the new RestClient / Rest5Client API
where applicable (search for code referencing classes from
elasticsearch-rest-client and adapt calls to the RestClient API, updating
imports and client construction accordingly); also run a dependency tree scan
(mvn dependency:tree) and security scan to confirm there are no problematic
transitive CVEs after the upgrade.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Line 249: The HealthService currently returns a hardcoded Elasticsearch
version string in the HealthCheckResult; update the code in HealthService where
it constructs new HealthCheckResult(...) to either (a) issue a GET "/" (or
another version endpoint) to the Elasticsearch cluster, parse the JSON response
to extract the "version.number" field, and pass that value instead of the
literal "Elasticsearch 8.10.0", or (b) remove the guessed version and pass
null/omit the version like the MySQL/Redis checks; implement the JSON parsing
and error handling where the current return new HealthCheckResult(true,
"Elasticsearch 8.10.0", null) occurs so HealthCheckResult receives the real
version string (or null) instead of the hardcoded value.
- Around line 94-119: The Elasticsearch RestClient opened in
initializeElasticsearchClient (assigned to the elasticsearchRestClient field) is
never closed; add a `@PreDestroy` lifecycle method (e.g., destroy or
closeResources) that checks if elasticsearchRestClient is non-null and open,
calls close() inside a try/catch, logs any errors, and sets
elasticsearchClientReady to false; ensure the method also mirrors the
ExecutorService cleanup pattern used elsewhere so resources are reliably
released on shutdown.
- Line 63: The static ExecutorService created with
Executors.newFixedThreadPool(4) (executorService) is never shut down and will
leak threads on WAR redeploys; change it to an instance field or add a lifecycle
cleanup method that shuts it down and closes related resources: add a
`@PreDestroy-annotated` method (e.g., cleanup) that calls
executorService.shutdownNow() (or orderly shutdown with timeout), handles
InterruptedException, and also closes elasticsearchRestClient with try/catch
logging any IOException to prevent resource leaks on undeploy/redeploy.
- Around line 265-311: performHealthCheck currently exposes raw error messages
into the returned details map; change its signature to accept a boolean
includeDetails and use that flag to avoid placing sensitive error strings into
details (but continue logging full errors internally). Specifically, update
performHealthCheck(componentName, details, checker) ->
performHealthCheck(componentName, details, checker, includeDetails), and when
result.isHealthy is false or an exception is caught, set details.put("error",
includeDetails ? safeErrorOrExceptionMessage : "suppressed") and set
details.put("errorType", includeDetails ? "CheckFailed"/"InternalError" :
"Sensitive"); keep logger.error/logger.warn calls unchanged so full diagnostics
remain in logs but not returned to unauthenticated callers. Ensure you handle
HealthCheckResult.error, e.getCause()/e.getMessage() branches accordingly so no
internal messages are added to details when includeDetails is false.

In `@src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java`:
- Line 89: There is a typo in the log message inside JwtUserIdValidationFilter:
change the logger.info call that currently logs "Common-API incoming userAget:
{}" to correctly spell "userAgent" so it reads "Common-API incoming userAgent:
{}", keeping the same logger invocation and the userAgent variable reference.
- Around line 48-53: In JwtUserIdValidationFilter, fix the logger typo by
renaming "userAget" to "userAgent" and update the public-endpoint matching logic
that currently uses path.endsWith("/health") and path.endsWith("/version"):
replace the permissive suffix checks with stricter checks (e.g., exact equals or
a defined prefix match such as path.equals("/health") / path.equals("/version")
or path.startsWith("/public/") as appropriate) so JWT validation isn't
accidentally bypassed; update the logger.info call that references the path to
use the corrected "userAgent" variable name.

---

Nitpick comments:
In
`@src/main/java/com/iemr/common/identity/controller/health/HealthController.java`:
- Around line 94-127: The isUserAuthenticated method duplicates JWT extraction
logic from JwtUserIdValidationFilter; refactor by adding a shared extractor
(e.g., JwtAuthenticationUtil.extractTokenFromRequest(HttpServletRequest)) that
encapsulates reading JwtToken header, Authorization Bearer header, and cookies
(preserving current precedence), then update
HealthController.isUserAuthenticated to call that extractor and then
jwtAuthenticationUtil.validateUserIdAndJwtToken(token); also modify
JwtUserIdValidationFilter to use the same new extractor so token parsing logic
is maintained in one place.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Around line 121-154: The health checks in checkHealth currently run
sequentially; modify checkHealth to run checkMySQLHealth, checkRedisHealth (only
if redisTemplate!=null) and checkElasticsearchHealth (only if
elasticsearchEnabled && elasticsearchClientReady) concurrently using
CompletableFuture.supplyAsync with your existing ExecutorService and combine
them with CompletableFuture.allOf, then gather each result future (handling
exceptions by returning a failing component map) and populate components and
overallHealth using isHealthy; ensure timestamp, STATUS_KEY, logging and
includeDetails behavior remain the same and that exceptions are logged and
translated into component DOWN entries so a hung call doesn't block the whole
check.
- Around line 362-416: The current manual parsing in extractHost, extractPort,
and extractDatabaseName is fragile; replace it by stripping the "jdbc:" prefix
and using java.net.URI to parse the remaining URI (or URI.create) so you
correctly handle credentials, IPv6, and multi-host forms: in extractHost call
new URI(withoutJdbc).getHost() and return UNKNOWN_VALUE if null, in extractPort
call new URI(...).getPort() and return "3306" when getPort() returns -1, and in
extractDatabaseName use new URI(...).getPath() (trim leading '/') and strip any
query component; wrap URI creation in try/catch and log debug on failure
preserving existing fallback values so behavior remains safe for malformed
inputs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pom.xml`:
- Around line 248-253: The pom dependency for
org.elasticsearch.client:elasticsearch-rest-client should be upgraded from
8.10.0 to 9.3.0 and the codebase evaluated for API changes (consider migrating
to the newer Rest5Client API). Update the
<artifactId>org.elasticsearch.client:elasticsearch-rest-client</artifactId>
<version> to 9.3.0 in the pom, run your full build/tests, and search for usages
of classes/methods from RestClient/RestClientBuilder to adapt to breaking
changes (replace with Rest5Client equivalents where needed); fix
compilation/test failures and update any configuration or client instantiation
code accordingly. Ensure compatibility by reading the 9.x migration notes and
adjust serialization/response handling if APIs differ.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java (2)

46-46: Consider downgrading per-request path logging to DEBUG.

logger.info(...) on every inbound request will produce a high-volume log stream in production. logger.debug(...) keeps the detail available during troubleshooting without polluting production logs.

♻️ Proposed change
- logger.info("JwtUserIdValidationFilter invoked for path: {}", path);
+ logger.debug("JwtUserIdValidationFilter invoked for path: {}", path);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java`
at line 46, The per-request logging in JwtUserIdValidationFilter uses
logger.info("JwtUserIdValidationFilter invoked for path: {}", path) which is too
noisy for production; change this call to logger.debug(...) so the path is
logged at DEBUG level (leave the message text and the path variable unchanged)
to keep the detail available for troubleshooting without polluting INFO logs.

45-53: Consider using getServletPath() or stripping the context path for more robust public-endpoint bypass.

The code uses getRequestURI() to check for public endpoints (/health, /version), but getRequestURI() includes the servlet context path. While the application currently deploys with the default root context, this creates a latent bug: if server.servlet.context-path is ever configured (e.g., to /identity), getRequestURI() would return /identity/health, causing path.equals("/health") to fail silently. Requests to these endpoints would then be incorrectly rejected with 401.

A more defensive approach is to use getServletPath() directly (which excludes context path), or explicitly strip the context path:

- String path = request.getRequestURI();
+ String path = request.getServletPath();

This ensures the bypass works correctly regardless of context-path configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java`
around lines 45 - 53, The current JwtUserIdValidationFilter uses
request.getRequestURI() to compute path which includes the context path and can
break the public-endpoint checks; change the logic to use
request.getServletPath() (or strip request.getContextPath() from
getRequestURI()) when computing the path variable used in the comparisons, then
keep the existing checks for "/health" and "/version" and call
filterChain.doFilter(servletRequest, servletResponse) as before; update the
logger line to log the servletPath (or stripped path) and adjust any references
to the path variable accordingly so JwtUserIdValidationFilter correctly bypasses
public endpoints regardless of context-path.
src/main/java/com/iemr/common/identity/service/health/HealthService.java (2)

500-510: Elasticsearch health check only verifies connectivity, not cluster health status.

/_cluster/health returns a JSON body with a "status" field (green, yellow, red). Currently, any HTTP 200 response reports the component as UP, even if the cluster is in red state. Consider parsing the response body and reflecting degraded status.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 500 - 510, The Elasticsearch health check currently treats any HTTP
200 from elasticsearchRestClient.performRequest(request) as UP; update
HealthService to parse the response body JSON (from response.getEntity()) and
inspect the "status" field returned by "/_cluster/health" instead of relying
solely on statusCode: map "green" => return new HealthCheckResult(true,...),
"yellow" => return new HealthCheckResult(false, "DEGRADED", "<status>"), and
"red" => return new HealthCheckResult(false, null, "<status>") (or similar
semantics used elsewhere); ensure to handle JSON parsing exceptions and non-200
responses by falling back to the existing HTTP error path and include
ELASTICSEARCH_TYPE and any parse error details in the log; touch methods/vars:
HealthService, elasticsearchRestClient.performRequest, ELASTICSEARCH_TYPE,
HealthCheckResult.

134-167: Health checks run sequentially — consider parallel execution.

All three component checks (MySQL, Redis, Elasticsearch) execute sequentially, so worst-case latency is the sum of all timeouts (~9s+). Since you already have an ExecutorService, you could submit them as CompletableFuture tasks and join, cutting latency to the single slowest component.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 134 - 167, The health checks currently call checkMySQLHealth,
checkRedisHealth and checkElasticsearchHealth sequentially; change to run them
in parallel via CompletableFuture.supplyAsync(..., executorService) (or your
existing ExecutorService) for each enabled component, then join all futures
(CompletableFuture.allOf or join per-future), collect each result into the
components map, and compute overallHealth by invoking isHealthy on each returned
Map; preserve the existing STATUS_KEY/timestamp/components population and
logger.info call after futures complete. Ensure exceptions from futures are
handled (wrap failures into a down-status Map for that component) so one failing
future doesn't break aggregation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Around line 198-297: The methods for advanced DB health
(addAdvancedMySQLMetrics, isMySQLDatabase, addDeepMySQLHealthChecks,
checkTableLocks, checkStuckProcesses, checkInnoDBStatus,
getMySQLStatusVariables, getMySQLVersion, getRedisVersionWithTimeout,
extractHost, extractPort, extractDatabaseName) are dead code in this PR — either
remove them now or wire them into the class before merging; if you keep them,
fix the internal issues: in addAdvancedMySQLMetrics remove the unguarded
double-parsing of Threads_connected and max_connections (the first
Integer.parseInt call), wrap parsing of slowQueries and questions in try/catch
like uptime to guard NumberFormatException, and update checkInnoDBStatus/any
query using INFORMATION_SCHEMA.INNODB_LOCKS to use performance_schema.data_locks
for MySQL 8+ compatibility. Ensure references are only kept if the methods are
actually called (or annotate/feature-flag them) to avoid shipping dead code.

---

Duplicate comments:
In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Around line 558-579: Both buildUnhealthyStatus and buildExceptionStatus ignore
the includeDetails flag and always expose sensitive error information in the
response; update them to only add sensitive fields to the returned details map
when includeDetails is true. Specifically, in buildUnhealthyStatus (method name)
only call details.put("error", ...) and details.put("errorType", ...) when
includeDetails is true (still set status to STATUS_DOWN and log the warning as
before); in buildExceptionStatus (method name) always set status and
responseTimeMs but only put the detailed error message into details
(details.put("error", ...) and any stack/exception-specific fields) when
includeDetails is true—otherwise set a generic non-sensitive error like "Health
check failed" in the response and ensure status.put("details", details) remains
correct. Ensure logging (logger.warn/logger.error) can remain for internal logs
but do not expose exception.getMessage()/getCause() in the response unless
includeDetails is true.

In `@src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java`:
- Around line 86-89: The earlier typos and logging style issues have been
resolved: ensure the variable name is consistently userAgent (references in
JwtUserIdValidationFilter) and that both logger calls use SLF4J placeholder
syntax (logger.info("User-Agent: {}", userAgent) and logger.info("Common-API
incoming userAgent: {}", userAgent)); no further code changes required beyond
verifying there are no remaining userAget occurrences and that all log
statements in JwtUserIdValidationFilter use placeholders rather than string
concatenation.

---

Nitpick comments:
In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Around line 500-510: The Elasticsearch health check currently treats any HTTP
200 from elasticsearchRestClient.performRequest(request) as UP; update
HealthService to parse the response body JSON (from response.getEntity()) and
inspect the "status" field returned by "/_cluster/health" instead of relying
solely on statusCode: map "green" => return new HealthCheckResult(true,...),
"yellow" => return new HealthCheckResult(false, "DEGRADED", "<status>"), and
"red" => return new HealthCheckResult(false, null, "<status>") (or similar
semantics used elsewhere); ensure to handle JSON parsing exceptions and non-200
responses by falling back to the existing HTTP error path and include
ELASTICSEARCH_TYPE and any parse error details in the log; touch methods/vars:
HealthService, elasticsearchRestClient.performRequest, ELASTICSEARCH_TYPE,
HealthCheckResult.
- Around line 134-167: The health checks currently call checkMySQLHealth,
checkRedisHealth and checkElasticsearchHealth sequentially; change to run them
in parallel via CompletableFuture.supplyAsync(..., executorService) (or your
existing ExecutorService) for each enabled component, then join all futures
(CompletableFuture.allOf or join per-future), collect each result into the
components map, and compute overallHealth by invoking isHealthy on each returned
Map; preserve the existing STATUS_KEY/timestamp/components population and
logger.info call after futures complete. Ensure exceptions from futures are
handled (wrap failures into a down-status Map for that component) so one failing
future doesn't break aggregation.

In `@src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java`:
- Line 46: The per-request logging in JwtUserIdValidationFilter uses
logger.info("JwtUserIdValidationFilter invoked for path: {}", path) which is too
noisy for production; change this call to logger.debug(...) so the path is
logged at DEBUG level (leave the message text and the path variable unchanged)
to keep the detail available for troubleshooting without polluting INFO logs.
- Around line 45-53: The current JwtUserIdValidationFilter uses
request.getRequestURI() to compute path which includes the context path and can
break the public-endpoint checks; change the logic to use
request.getServletPath() (or strip request.getContextPath() from
getRequestURI()) when computing the path variable used in the comparisons, then
keep the existing checks for "/health" and "/version" and call
filterChain.doFilter(servletRequest, servletResponse) as before; update the
logger line to log the servletPath (or stripped path) and adjust any references
to the path variable accordingly so JwtUserIdValidationFilter correctly bypasses
public endpoints regardless of context-path.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/main/java/com/iemr/common/identity/service/health/HealthService.java (2)

104-121: Consider upgrading elasticsearch-rest-client from 8.10.0 to the latest stable patch.

The latest stable release with a matching low-level rest client is 8.16.6. Staying on 8.10.0 means missing several maintenance and security patches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 104 - 121, The project is using elasticsearch-rest-client 8.10.0
and should be upgraded to the latest stable matching low-level client (8.16.6);
update the dependency/version in your build file (pom.xml or build.gradle) to
8.16.6, then run the build (mvn/gradle) and tests, and fix any API changes
surfaced in initializeElasticsearchClient or usages of elasticsearchRestClient
and elasticsearchClientReady (adjust imports or method signatures if needed) to
ensure the RestClient instantiation and timeouts still compile and behave as
before.

263-273: Remove unused errorMessage variable.

errorMessage is computed at line 267 but never put into the status map or used anywhere. It's a leftover from the prior refactor that removed error exposure to callers.

♻️ Proposed cleanup
 } catch (Exception e) {
     long responseTime = System.currentTimeMillis() - startTime;
     logger.error("{} health check failed with exception: {}", componentName, e.getMessage(), e);
-    
-    String errorMessage = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
 
     status.put(STATUS_KEY, STATUS_DOWN);
     status.put("responseTimeMs", responseTime);
     
     return status;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 263 - 273, In HealthService
(src/main/java/com/iemr/common/identity/service/health/HealthService.java)
remove the unused local variable errorMessage (the line computing e.getCause() ?
... assigned to errorMessage) from the catch block so the code does not compute
a value that's never used; leave the existing logger.error,
status.put(STATUS_KEY, STATUS_DOWN) and responseTime logic unchanged and run
tests/compile to confirm no unused-variable warnings remain.
src/main/java/com/iemr/common/identity/controller/health/HealthController.java (1)

49-57: Remove unused jwtAuthenticationUtil dependency and HttpServletRequest parameter.

jwtAuthenticationUtil is injected but never called in any method. Likewise, the HttpServletRequest request parameter in checkHealth is never accessed. Both are dead code.

The injected JwtAuthenticationUtil is a particular concern: since /health is already whitelisted in the JWT filter, no auth is needed here. More critically, if JwtAuthenticationUtil has transitive dependencies that fail to initialize, the HealthController itself will fail to start — directly defeating the purpose of a health endpoint.

♻️ Proposed cleanup
 `@RestController`
 `@RequestMapping`("/health")
 `@Tag`(name = "Health Check", description = "APIs for checking infrastructure health status")
 public class HealthController {

     private static final Logger logger = LoggerFactory.getLogger(HealthController.class);

     private final HealthService healthService;
-    private final JwtAuthenticationUtil jwtAuthenticationUtil;
     
-    public HealthController(HealthService healthService, JwtAuthenticationUtil jwtAuthenticationUtil) {
+    public HealthController(HealthService healthService) {
         this.healthService = healthService;
-        this.jwtAuthenticationUtil = jwtAuthenticationUtil;
     }

     `@GetMapping`
     ...
-    public ResponseEntity<Map<String, Object>> checkHealth(HttpServletRequest request) {
+    public ResponseEntity<Map<String, Object>> checkHealth() {

Also remove the now-unused imports:

-import jakarta.servlet.http.Cookie;
-import jakarta.servlet.http.HttpServletRequest;
-import com.iemr.common.identity.utils.JwtAuthenticationUtil;

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/iemr/common/identity/controller/health/HealthController.java`
around lines 49 - 57, Remove the unused JwtAuthenticationUtil injection and the
unused HttpServletRequest parameter from the HealthController: delete the
private final JwtAuthenticationUtil jwtAuthenticationUtil field and its
constructor parameter in HealthController(HealthService healthService,
JwtAuthenticationUtil jwtAuthenticationUtil) (and update the constructor to only
accept HealthService), and remove the HttpServletRequest request parameter from
the checkHealth(...) method signature; also remove any now-unused imports
related to JwtAuthenticationUtil and HttpServletRequest so the controller has no
dead dependencies that could break startup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/java/com/iemr/common/identity/controller/health/HealthController.java`:
- Around line 49-57: Remove the unused JwtAuthenticationUtil injection and the
unused HttpServletRequest parameter from the HealthController: delete the
private final JwtAuthenticationUtil jwtAuthenticationUtil field and its
constructor parameter in HealthController(HealthService healthService,
JwtAuthenticationUtil jwtAuthenticationUtil) (and update the constructor to only
accept HealthService), and remove the HttpServletRequest request parameter from
the checkHealth(...) method signature; also remove any now-unused imports
related to JwtAuthenticationUtil and HttpServletRequest so the controller has no
dead dependencies that could break startup.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Around line 104-121: The project is using elasticsearch-rest-client 8.10.0 and
should be upgraded to the latest stable matching low-level client (8.16.6);
update the dependency/version in your build file (pom.xml or build.gradle) to
8.16.6, then run the build (mvn/gradle) and tests, and fix any API changes
surfaced in initializeElasticsearchClient or usages of elasticsearchRestClient
and elasticsearchClientReady (adjust imports or method signatures if needed) to
ensure the RestClient instantiation and timeouts still compile and behave as
before.
- Around line 263-273: In HealthService
(src/main/java/com/iemr/common/identity/service/health/HealthService.java)
remove the unused local variable errorMessage (the line computing e.getCause() ?
... assigned to errorMessage) from the catch block so the code does not compute
a value that's never used; leave the existing logger.error,
status.put(STATUS_KEY, STATUS_DOWN) and responseTime logic unchanged and run
tests/compile to confirm no unused-variable warnings remain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/main/java/com/iemr/common/identity/service/health/HealthService.java (4)

103-120: Hardcoded "http" scheme prevents HTTPS connections to Elasticsearch.

Line 106 hardcodes the scheme to "http". Production Elasticsearch clusters commonly require TLS. Consider making the scheme configurable.

Proposed fix

Add a new @Value property:

`@Value`("${elasticsearch.scheme:http}") String elasticsearchScheme

Then use it:

-    new HttpHost(elasticsearchHost, elasticsearchPort, "http")
+    new HttpHost(elasticsearchHost, elasticsearchPort, elasticsearchScheme)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 103 - 120, initializeElasticsearchClient currently hardcodes the
scheme "http", preventing TLS connections; add a new injectable field (e.g.
`@Value`("${elasticsearch.scheme:http}") String elasticsearchScheme) to the class
and replace the hardcoded "http" in the HttpHost(...) call with
elasticsearchScheme so the method uses the configured scheme (allowing "https"
in production) and retains "http" as the default.

152-152: INFO-level log on every health check will be noisy in production.

Health endpoints are typically polled every few seconds by load balancers and orchestration platforms. Logging at INFO on each invocation produces excessive log volume. Consider DEBUG here, or log at INFO only on status transitions.

Proposed fix
-        logger.info("Health check completed - Overall status: {}", overallHealth ? STATUS_UP : STATUS_DOWN);
+        logger.debug("Health check completed - Overall status: {}", overallHealth ? STATUS_UP : STATUS_DOWN);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java` at
line 152, The health check currently logs every invocation at INFO using
logger.info("Health check completed - Overall status: {}", overallHealth ?
STATUS_UP : STATUS_DOWN) which is noisy; change the logging strategy in
HealthService (the method performing the health check that sets overallHealth)
to either log at DEBUG for every check or emit INFO only when the status changes
(compare previous status to overallHealth before logging and update previous
status), using the same message format with STATUS_UP/STATUS_DOWN so you retain
context.

91-101: shutdownNow() without awaitTermination — in-flight tasks may not terminate cleanly.

If a Redis health check is in progress when the context shuts down, shutdownNow() only requests cancellation. Without awaitTermination, the @PreDestroy method returns immediately and the container may proceed with shutdown while pool threads are still running.

Proposed fix
 `@jakarta.annotation.PreDestroy`
 public void cleanup() {
     executorService.shutdownNow();
+    try {
+        if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
+            logger.warn("ExecutorService did not terminate within timeout");
+        }
+    } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+    }
     if (elasticsearchRestClient != null) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 91 - 101, The cleanup() method calls executorService.shutdownNow()
without waiting for in-flight tasks to finish; change it to first call
executorService.shutdown(), then await termination with a reasonable timeout
(e.g., via executorService.awaitTermination(timeout, unit)), and only call
shutdownNow() if awaitTermination returns false; also catch
InterruptedException, log it via logger.warn in cleanup(), and restore the
thread interrupt status; keep the existing elasticsearchRestClient.close()
handling as-is.

277-285: HealthCheckResult could be converted to a Java record.

The project targets Java 17, which fully supports records. This inner class is a good candidate for a record, eliminating boilerplate code.

Proposed change
-    private static class HealthCheckResult {
-        final boolean isHealthy;
-        final String error;
-
-        HealthCheckResult(boolean isHealthy, String error) {
-            this.isHealthy = isHealthy;
-            this.error = error;
-        }
-    }
+    private record HealthCheckResult(boolean isHealthy, String error) {}

Accessors at lines 251 and 255 would change from field access to method calls:

  • result.isHealthyresult.isHealthy()
  • result.errorresult.error()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`
around lines 277 - 285, Convert the inner static class HealthCheckResult into a
Java record to remove boilerplate: replace "private static class
HealthCheckResult { final boolean isHealthy; final String error; ... }" with
"private static record HealthCheckResult(boolean isHealthy, String error) {}",
then update all call sites to use the record accessor methods (e.g., change
result.isHealthy to result.isHealthy() and result.error to result.error()).
Ensure constructors/usages that instantiate HealthCheckResult remain valid using
the record's canonical constructor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/com/iemr/common/identity/service/health/HealthService.java`:
- Around line 103-120: initializeElasticsearchClient currently hardcodes the
scheme "http", preventing TLS connections; add a new injectable field (e.g.
`@Value`("${elasticsearch.scheme:http}") String elasticsearchScheme) to the class
and replace the hardcoded "http" in the HttpHost(...) call with
elasticsearchScheme so the method uses the configured scheme (allowing "https"
in production) and retains "http" as the default.
- Line 152: The health check currently logs every invocation at INFO using
logger.info("Health check completed - Overall status: {}", overallHealth ?
STATUS_UP : STATUS_DOWN) which is noisy; change the logging strategy in
HealthService (the method performing the health check that sets overallHealth)
to either log at DEBUG for every check or emit INFO only when the status changes
(compare previous status to overallHealth before logging and update previous
status), using the same message format with STATUS_UP/STATUS_DOWN so you retain
context.
- Around line 91-101: The cleanup() method calls executorService.shutdownNow()
without waiting for in-flight tasks to finish; change it to first call
executorService.shutdown(), then await termination with a reasonable timeout
(e.g., via executorService.awaitTermination(timeout, unit)), and only call
shutdownNow() if awaitTermination returns false; also catch
InterruptedException, log it via logger.warn in cleanup(), and restore the
thread interrupt status; keep the existing elasticsearchRestClient.close()
handling as-is.
- Around line 277-285: Convert the inner static class HealthCheckResult into a
Java record to remove boilerplate: replace "private static class
HealthCheckResult { final boolean isHealthy; final String error; ... }" with
"private static record HealthCheckResult(boolean isHealthy, String error) {}",
then update all call sites to use the record accessor methods (e.g., change
result.isHealthy to result.isHealthy() and result.error to result.error()).
Ensure constructors/usages that instantiate HealthCheckResult remain valid using
the record's canonical constructor.

@sonarqubecloud
Copy link

@DurgaPrasad-54
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@drtechie drtechie merged commit 2ed618d into PSMRI:main Mar 2, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants