Skip to content

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

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

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

Conversation

@DurgaPrasad-54
Copy link
Contributor

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

📋 Description

JIRA ID:

This PR implements /health and /version endpoints in the TM-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 reporting per-component status, response times, errors and overall service health.
  • Refactor

    • Version endpoint now returns structured JSON with buildTimestamp, version, branch and commitHash; unknown values handled consistently.
  • Chores

    • Build updated to include selected Git metadata in artifacts and adjust artifact naming.
  • Bug Fixes

    • JWT validation skip list extended to exempt /version and /health endpoints.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 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
📝 Walkthrough

Walkthrough

Adds /health and /version REST endpoints, a concurrent HealthService performing MySQL and Redis checks with timeouts and throttled diagnostics, updates JWT filter to skip these endpoints, and updates pom.xml to change artifact finalName and add git-commit-id-maven-plugin.

Changes

Cohort / File(s) Summary
Build Configuration
pom.xml
Changed <finalName> to ${project.artifactId}-${project.version} and added io.github.git-commit-id:git-commit-id-maven-plugin (v9.0.2) with an execution in the initialize phase to generate git.properties containing git.branch, git.commit.id.abbrev, git.build.version, git.build.time and fail-safes disabled.
Health Service & Controller
src/main/java/com/iemr/tm/service/health/HealthService.java, src/main/java/com/iemr/tm/controller/health/HealthController.java
Added HealthService that runs concurrent MySQL and Redis checks with per-check timeouts, aggregate deadline, throttled advanced diagnostics, component-level results and overall UP/DEGRADED/DOWN aggregation; HealthController exposes /health and maps status to HTTP 200 or 503.
Version Controller
src/main/java/com/iemr/tm/controller/version/VersionController.java
Changed /version to return ResponseEntity<Map<String,String>>, loads git.properties into Properties, returns buildTimestamp, version, branch, commitHash, and uses UNKNOWN_VALUE on errors.
JWT Filter
src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java
Extended skip logic to bypass JWT validation for /version and /health endpoints in addition to existing public paths.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HealthController
    participant HealthService
    participant ExecutorService
    participant MySQL
    participant Redis

    Client->>HealthController: GET /health
    HealthController->>HealthService: checkHealth()
    HealthService->>ExecutorService: submit MySQL check task
    HealthService->>ExecutorService: submit Redis check task

    par MySQL Check
        ExecutorService->>MySQL: SELECT 1 (3s timeout)
        MySQL-->>ExecutorService: result / timeout
    and Redis Check
        ExecutorService->>Redis: PING (3s timeout)
        Redis-->>ExecutorService: PONG / timeout
    end

    ExecutorService-->>HealthService: MySQL HealthCheckResult
    ExecutorService-->>HealthService: Redis HealthCheckResult
    HealthService->>HealthService: aggregate component statuses -> overall status
    HealthService-->>HealthController: health map (timestamp, components, status)
    HealthController-->>Client: HTTP 200 (UP/DEGRADED) or 503 (DOWN) with JSON
Loading
sequenceDiagram
    participant Client
    participant VersionController
    participant GitPropertiesFile

    Client->>VersionController: GET /version
    VersionController->>GitPropertiesFile: load git.properties
    GitPropertiesFile-->>VersionController: Properties object
    VersionController->>VersionController: extract branch, commitHash, version, buildTimestamp
    VersionController-->>Client: ResponseEntity<Map> (200 JSON)
    Note over VersionController: On exception, populate fields with UNKNOWN_VALUE
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Implements /health and /version endpoints and adds Maven git metadata generation.

Poem

🐰 I hop and check the database and Redis near,
I fetch the git stamp so the version's clear,
Executors scurry, timeouts keep the pace,
Components report in—status lights in place,
Hooray, the warren's services are here!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title contains a typo: 'healt' should be 'health'. This makes it unclear and appears unprofessional despite the core message being relevant. Correct the typo in the title to 'feat(health,version): add health and version endpoints' for clarity and professionalism.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 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
Contributor

@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 (5)
src/main/java/com/iemr/tm/service/health/HealthService.java (3)

68-71: shutdown() should await termination to let in-flight checks complete.

Calling shutdown() without awaitTermination() means the @PreDestroy callback returns immediately, and in-flight health-check tasks may be interrupted mid-connection by the JVM exit.

♻️ Suggested fix
     `@PreDestroy`
     public void shutdown() {
         executorService.shutdown();
+        try {
+            if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
+                executorService.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            executorService.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 68
- 71, The shutdown() `@PreDestroy` currently calls executorService.shutdown() but
returns immediately; update shutdown() to call executorService.shutdown() then
awaitTermination with a sensible timeout (e.g., a few seconds) to allow
in-flight health checks to complete, handle InterruptedException by restoring
the interrupt status and calling executorService.shutdownNow() if needed, and
log if the executor did not terminate within the timeout; refer to the
shutdown() method and executorService field when making this change.

99-133: Double-timeout: JDBC setQueryTimeout and future.get() both use 3 s.

If the JDBC driver honors the query timeout, it fires at ~3 s. The future.get() timeout also fires at 3 s. In a tight race the outer TimeoutException may win, hiding the more informative JDBC timeout message. Consider making the JDBC timeout slightly shorter (e.g., MYSQL_TIMEOUT_SECONDS - 1) so the inner timeout triggers first.

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 99
- 133, In checkMySQLHealth adjust the two timeouts so the JDBC driver’s
stmt.setQueryTimeout fires before the outer future.get: set the
PreparedStatement timeout to something slightly shorter (e.g.,
setQueryTimeout((int)Math.max(1, MYSQL_TIMEOUT_SECONDS - 1))) and keep
future.get(MYSQL_TIMEOUT_SECONDS, TimeUnit.SECONDS) unchanged; update any
related logging/messages if needed so the more informative JDBC timeout (thrown
inside the CompletableFuture) is observed instead of the outer TimeoutException.

73-97: Health checks run sequentially despite the async machinery — consider parallelizing.

checkMySQLHealth() submits to the executor and immediately blocks on future.get(), then checkRedisHealth() does the same. This means worst-case latency is MYSQL_TIMEOUT + REDIS_TIMEOUT (6 s). Since you already have an executor, you can fire both futures simultaneously and join afterward to cut latency roughly in half.

♻️ Sketch: parallel health checks
 public Map<String, Object> checkHealth() {
     Map<String, Object> response = new LinkedHashMap<>();
     response.put("timestamp", Instant.now().toString());
     
     Map<String, Map<String, Object>> components = new LinkedHashMap<>();
     
-    // Check MySQL
-    Map<String, Object> mysqlStatus = new LinkedHashMap<>();
-    performHealthCheck("MySQL", mysqlStatus, this::checkMySQLHealth);
-    components.put("mysql", mysqlStatus);
-    
-    // Check Redis
-    Map<String, Object> redisStatus = new LinkedHashMap<>();
-    performHealthCheck("Redis", redisStatus, this::checkRedisHealth);
-    components.put("redis", redisStatus);
+    // Fire both checks in parallel
+    CompletableFuture<Map<String, Object>> mysqlFuture = CompletableFuture.supplyAsync(() -> {
+        Map<String, Object> s = new LinkedHashMap<>();
+        performHealthCheck("MySQL", s, this::checkMySQLHealth);
+        return s;
+    }, executorService);
+    
+    CompletableFuture<Map<String, Object>> redisFuture = CompletableFuture.supplyAsync(() -> {
+        Map<String, Object> s = new LinkedHashMap<>();
+        performHealthCheck("Redis", s, this::checkRedisHealth);
+        return s;
+    }, executorService);
+    
+    components.put("mysql", mysqlFuture.join());
+    components.put("redis", redisFuture.join());
     
     response.put("components", components);

Note: if you parallelize this way, the inner checkMySQLHealth/checkRedisHealth methods would need to run their work on the calling thread (or you'd need a larger pool), since the outer future already occupies a pool thread.

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 73
- 97, The checkHealth method currently calls performHealthCheck sequentially
which submits work and immediately blocks; instead submit both health tasks to
the executor first (e.g., create two Futures by submitting Callables that invoke
checkMySQLHealth and checkRedisHealth or wrappers that call those methods) so
both run in parallel, then wait/join both futures and populate the mysql and
redis maps with their results; ensure checkMySQLHealth and checkRedisHealth
perform their work on the calling thread (or increase the executor pool) so you
don't double-occupy pool threads, and keep using performHealthCheck/isHealthy
logic to set component status and the overall STATUS_KEY after both futures
complete.
src/main/java/com/iemr/tm/controller/health/HealthController.java (1)

61-61: Lower log level to debug — health probes typically poll every few seconds.

Kubernetes liveness/readiness probes (or similar monitors) will hit /health frequently. logger.info on every invocation will produce significant log noise in production. The completion log on line 69 already uses debug.

♻️ Suggested fix
-        logger.info("Health check endpoint called");
+        logger.debug("Health check endpoint called");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/controller/health/HealthController.java` at line
61, The health check log at the start of the health endpoint should be lowered
from info to debug to avoid noise; locate the HealthController class and the
method that handles the /health endpoint (the line with logger.info("Health
check endpoint called")) and change that call to logger.debug(...) so it matches
the existing debug-level completion log and reduces frequent probe logging.
src/main/java/com/iemr/tm/controller/version/VersionController.java (1)

49-67: Consider caching git properties — they are immutable for the lifetime of the process.

git.properties is baked into the artifact at build time and never changes at runtime, yet loadGitProperties() re-reads the classpath resource on every call. Loading once at construction (or lazily) avoids repeated I/O on a potentially hot path (e.g., monitoring probes polling /version).

♻️ Proposed: load once at construction time
 `@RestController`
 public class VersionController {

 	private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
-	
 	private static final String UNKNOWN_VALUE = "unknown";
+	private final Map<String, String> versionInfo;
+
+	public VersionController() {
+		this.versionInfo = loadVersionInfo();
+	}

 	`@Operation`(summary = "Get version information")
 	`@GetMapping`(value = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
 	public ResponseEntity<Map<String, String>> versionInformation() {
-		Map<String, String> response = new LinkedHashMap<>();
-		try {
-			logger.info("version Controller Start");
-			Properties gitProperties = loadGitProperties();
-			response.put("buildTimestamp", gitProperties.getProperty("git.build.time", UNKNOWN_VALUE));
-			response.put("version", gitProperties.getProperty("git.build.version", UNKNOWN_VALUE));
-			response.put("branch", gitProperties.getProperty("git.branch", UNKNOWN_VALUE));
-			response.put("commitHash", gitProperties.getProperty("git.commit.id.abbrev", UNKNOWN_VALUE));
-		} catch (Exception e) {
-			logger.error("Failed to load version information", e);
-			response.put("buildTimestamp", UNKNOWN_VALUE);
-			response.put("version", UNKNOWN_VALUE);
-			response.put("branch", UNKNOWN_VALUE);
-			response.put("commitHash", UNKNOWN_VALUE);
-		}
-		logger.info("version Controller End");
-		return ResponseEntity.ok(response);
+		return ResponseEntity.ok(versionInfo);
+	}
+
+	private Map<String, String> loadVersionInfo() {
+		Map<String, String> info = new LinkedHashMap<>();
+		try {
+			Properties gitProperties = loadGitProperties();
+			info.put("buildTimestamp", gitProperties.getProperty("git.build.time", UNKNOWN_VALUE));
+			info.put("version", gitProperties.getProperty("git.build.version", UNKNOWN_VALUE));
+			info.put("branch", gitProperties.getProperty("git.branch", UNKNOWN_VALUE));
+			info.put("commitHash", gitProperties.getProperty("git.commit.id.abbrev", UNKNOWN_VALUE));
+		} catch (Exception e) {
+			logger.error("Failed to load version information", e);
+			info.put("buildTimestamp", UNKNOWN_VALUE);
+			info.put("version", UNKNOWN_VALUE);
+			info.put("branch", UNKNOWN_VALUE);
+			info.put("commitHash", UNKNOWN_VALUE);
+		}
+		return Map.copyOf(info);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/controller/version/VersionController.java` around
lines 49 - 67, The versionInformation() method repeatedly calls
loadGitProperties() on every request; change this to load and cache the
Properties once (either in the controller constructor or via a thread-safe lazy
init) so subsequent calls reuse the same Properties instance. Add a private
field (e.g., cachedGitProperties) and populate it at construction time (or
initialize it in a synchronized/volatile lazy getter), preserve the same
fallback to UNKNOWN_VALUE on load failure, and update versionInformation() to
read from cachedGitProperties instead of calling loadGitProperties() each time;
keep logging and exception handling consistent.
🤖 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/tm/service/health/HealthService.java`:
- Around line 135-138: checkRedisHealth currently reports Redis as healthy when
redisTemplate == null which is misleading; update checkRedisHealth in
HealthService so that when redisTemplate is null it does not return an "UP"
health result—either return a HealthCheckResult that indicates a NOT_CONFIGURED
/ distinct status (e.g., healthy=false with details="NOT_CONFIGURED") or return
null and adjust checkHealth to omit Redis from the composite response; locate
checkRedisHealth and the caller checkHealth to implement the chosen behavior and
ensure the response format remains consistent.

---

Nitpick comments:
In `@src/main/java/com/iemr/tm/controller/health/HealthController.java`:
- Line 61: The health check log at the start of the health endpoint should be
lowered from info to debug to avoid noise; locate the HealthController class and
the method that handles the /health endpoint (the line with logger.info("Health
check endpoint called")) and change that call to logger.debug(...) so it matches
the existing debug-level completion log and reduces frequent probe logging.

In `@src/main/java/com/iemr/tm/controller/version/VersionController.java`:
- Around line 49-67: The versionInformation() method repeatedly calls
loadGitProperties() on every request; change this to load and cache the
Properties once (either in the controller constructor or via a thread-safe lazy
init) so subsequent calls reuse the same Properties instance. Add a private
field (e.g., cachedGitProperties) and populate it at construction time (or
initialize it in a synchronized/volatile lazy getter), preserve the same
fallback to UNKNOWN_VALUE on load failure, and update versionInformation() to
read from cachedGitProperties instead of calling loadGitProperties() each time;
keep logging and exception handling consistent.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Around line 68-71: The shutdown() `@PreDestroy` currently calls
executorService.shutdown() but returns immediately; update shutdown() to call
executorService.shutdown() then awaitTermination with a sensible timeout (e.g.,
a few seconds) to allow in-flight health checks to complete, handle
InterruptedException by restoring the interrupt status and calling
executorService.shutdownNow() if needed, and log if the executor did not
terminate within the timeout; refer to the shutdown() method and executorService
field when making this change.
- Around line 99-133: In checkMySQLHealth adjust the two timeouts so the JDBC
driver’s stmt.setQueryTimeout fires before the outer future.get: set the
PreparedStatement timeout to something slightly shorter (e.g.,
setQueryTimeout((int)Math.max(1, MYSQL_TIMEOUT_SECONDS - 1))) and keep
future.get(MYSQL_TIMEOUT_SECONDS, TimeUnit.SECONDS) unchanged; update any
related logging/messages if needed so the more informative JDBC timeout (thrown
inside the CompletableFuture) is observed instead of the outer TimeoutException.
- Around line 73-97: The checkHealth method currently calls performHealthCheck
sequentially which submits work and immediately blocks; instead submit both
health tasks to the executor first (e.g., create two Futures by submitting
Callables that invoke checkMySQLHealth and checkRedisHealth or wrappers that
call those methods) so both run in parallel, then wait/join both futures and
populate the mysql and redis maps with their results; ensure checkMySQLHealth
and checkRedisHealth perform their work on the calling thread (or increase the
executor pool) so you don't double-occupy pool threads, and keep using
performHealthCheck/isHealthy logic to set component status and the overall
STATUS_KEY after both futures complete.

Copy link
Contributor

@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: 3

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

136-137: ConcurrentHashMap is unnecessary for per-task status maps.

Each map is written by exactly one executor thread and read by the main thread only after future.get(), which already establishes happens-before. A plain new LinkedHashMap<>() (consistent with the rest of the method) suffices and preserves insertion order.

♻️ Proposed fix
-        Map<String, Object> mysqlStatus = new ConcurrentHashMap<>();
-        Map<String, Object> redisStatus = new ConcurrentHashMap<>();
+        Map<String, Object> mysqlStatus = new LinkedHashMap<>();
+        Map<String, Object> redisStatus = new LinkedHashMap<>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 136
- 137, Replace the unnecessary ConcurrentHashMap instances used for per-task
status with a plain insertion-ordered map: change the declarations of
mysqlStatus and redisStatus from ConcurrentHashMap to LinkedHashMap (e.g., new
LinkedHashMap<>()) in HealthService (the variables mysqlStatus and redisStatus),
since each map is written by a single executor thread and read after
future.get(), so synchronization is not required and insertion order should be
preserved.

105-106: ADVANCED_HEALTH_CHECKS_ENABLED is a compile-time constant and cannot be toggled without recompiling.

The comment acknowledges this as a workaround, but @Value works correctly on instance fields in Spring. Making it configurable avoids having to redeploy to disable expensive advanced checks in a degraded environment.

♻️ Proposed fix
-    // Advanced health checks enabled - static to avoid Spring property parsing issues
-    private static final boolean ADVANCED_HEALTH_CHECKS_ENABLED = true;
+    `@Value`("${health.advanced-checks.enabled:true}")
+    private boolean advancedHealthChecksEnabled;

Then replace all ADVANCED_HEALTH_CHECKS_ENABLED references with advancedHealthChecksEnabled.

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 105
- 106, The ADVANCED_HEALTH_CHECKS_ENABLED is a static compile-time constant;
change it to an instance-level configurable property by adding a non-static
boolean field advancedHealthChecksEnabled in HealthService wired with `@Value`
(with a sensible default like true) and remove/replace all references to
ADVANCED_HEALTH_CHECKS_ENABLED with advancedHealthChecksEnabled so the flag can
be toggled via Spring properties at runtime; ensure the field is used in the
same methods that currently reference ADVANCED_HEALTH_CHECKS_ENABLED and that
the class remains Spring-managed so `@Value` is effective.
src/main/java/com/iemr/tm/controller/health/HealthController.java (2)

76-79: Error-fallback response is structurally inconsistent with the normal response — missing the "components" key.

Consumers that always expect components in the payload will fail to parse the exceptional 503. Adding a minimal components entry improves predictability.

♻️ Proposed fix
-            Map<String, Object> errorResponse = Map.of(
-                "status", "DOWN",
-                "timestamp", Instant.now().toString()
-            );
+            Map<String, Object> errorResponse = new java.util.LinkedHashMap<>();
+            errorResponse.put("timestamp", Instant.now().toString());
+            errorResponse.put("components", Map.of());
+            errorResponse.put("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/tm/controller/health/HealthController.java` around
lines 76 - 79, The error fallback in HealthController builds errorResponse
without the "components" field, making it structurally different from normal
responses; update the errorResponse Map construction (variable errorResponse in
HealthController) to include a minimal "components" entry (e.g., an empty Map or
the same shape as the normal components payload) alongside "status" and
"timestamp" so the 503 payload always contains "components" for consistent
consumer parsing.

61-61: Use logger.debug for the per-request entry log to avoid INFO-level noise from frequent health probes.

Load balancers and monitoring systems typically poll /health every few seconds per instance, flooding logs at INFO level.

♻️ Proposed fix
-        logger.info("Health check endpoint called");
+        logger.debug("Health check endpoint called");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/controller/health/HealthController.java` at line
61, Change the per-request entry log in the HealthController from INFO to DEBUG
to avoid INFO-level noise from frequent health probes: locate the health
endpoint method in class HealthController where logger.info("Health check
endpoint called") is used and replace that call with logger.debug(...) so
routine health probe hits are logged at debug level while preserving any
remaining INFO-level startup or error logs.
🤖 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/tm/service/health/HealthService.java`:
- Line 112: The fixed two-thread executor in HealthService (executorService
initialized via Executors.newFixedThreadPool(2)) causes concurrent /health
checks to queue and time out; replace that with an unbounded or virtual-thread
pool so per-request MySQL/Redis health tasks don't block each other—if on Java
21+ use a virtual-thread executor, otherwise change to
Executors.newCachedThreadPool(), and update any related imports/initialization
of executorService accordingly.
- Around line 407-412: The SELECT in HealthService (inside the try with
PreparedStatement used by hasLockWaits) compares PROCESSLIST.user to USER(), but
USER() returns 'user@host' while PROCESSLIST.user stores only the username, so
the predicate never matches; change the SQL to compare PROCESSLIST.user to
SUBSTRING_INDEX(USER(), '@', 1) (or otherwise extract the username) and consider
switching the query to performance_schema.processlist for forward compatibility;
update the PreparedStatement creation in the hasLockWaits method (and similarly
hasSlowQueries if applicable) to use the adjusted/replacement table and username
extraction so the filter correctly matches the current DB user.

---

Duplicate comments:
In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Around line 217-218: The HealthService health check currently returns new
HealthCheckResult(true, "Redis not configured — skipped", false) when
redisTemplate is null; no code change is needed because the review requirement
to surface a message for the not-configured case has been satisfied—confirm the
HealthService method that checks redisTemplate retains this message and that
HealthCheckResult(...) is used consistently elsewhere.

---

Nitpick comments:
In `@src/main/java/com/iemr/tm/controller/health/HealthController.java`:
- Around line 76-79: The error fallback in HealthController builds errorResponse
without the "components" field, making it structurally different from normal
responses; update the errorResponse Map construction (variable errorResponse in
HealthController) to include a minimal "components" entry (e.g., an empty Map or
the same shape as the normal components payload) alongside "status" and
"timestamp" so the 503 payload always contains "components" for consistent
consumer parsing.
- Line 61: Change the per-request entry log in the HealthController from INFO to
DEBUG to avoid INFO-level noise from frequent health probes: locate the health
endpoint method in class HealthController where logger.info("Health check
endpoint called") is used and replace that call with logger.debug(...) so
routine health probe hits are logged at debug level while preserving any
remaining INFO-level startup or error logs.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Around line 136-137: Replace the unnecessary ConcurrentHashMap instances used
for per-task status with a plain insertion-ordered map: change the declarations
of mysqlStatus and redisStatus from ConcurrentHashMap to LinkedHashMap (e.g.,
new LinkedHashMap<>()) in HealthService (the variables mysqlStatus and
redisStatus), since each map is written by a single executor thread and read
after future.get(), so synchronization is not required and insertion order
should be preserved.
- Around line 105-106: The ADVANCED_HEALTH_CHECKS_ENABLED is a static
compile-time constant; change it to an instance-level configurable property by
adding a non-static boolean field advancedHealthChecksEnabled in HealthService
wired with `@Value` (with a sensible default like true) and remove/replace all
references to ADVANCED_HEALTH_CHECKS_ENABLED with advancedHealthChecksEnabled so
the flag can be toggled via Spring properties at runtime; ensure the field is
used in the same methods that currently reference ADVANCED_HEALTH_CHECKS_ENABLED
and that the class remains Spring-managed so `@Value` is effective.

Copy link
Contributor

@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: 2

🤖 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/tm/service/health/HealthService.java`:
- Line 107: The constant ADVANCED_HEALTH_CHECKS_ENABLED is a compile-time true
and makes the runtime guard (the if (!ADVANCED_HEALTH_CHECKS_ENABLED) return
false; check) unreachable; remove the constant and the dead branch or replace
the constant with a runtime/config flag (e.g., read from application properties
or an env var) so the guard becomes meaningful. Specifically, delete
ADVANCED_HEALTH_CHECKS_ENABLED and the guarded return, or change
ADVANCED_HEALTH_CHECKS_ENABLED into a non-final field or config lookup and wire
it into the HealthService initialization so the existing guard that references
ADVANCED_HEALTH_CHECKS_ENABLED behaves correctly.
- Around line 407-433: The method
handleAdvancedChecksFuture(Future<AdvancedCheckResult> future) is missing its
final closing brace; add a single '}' to close the method after the last
catch(Exception ex) block (i.e., immediately after the existing catch block that
logs "Advanced MySQL checks failed") so the next method/field declarations are
outside handleAdvancedChecksFuture; locate the method by its name and references
to ADVANCED_CHECKS_TIMEOUT_MS and logger to find the correct spot.

---

Duplicate comments:
In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Line 113: The fixed-size pool created in HealthService (executorService =
Executors.newFixedThreadPool(6)) limits concurrent /health handling and can
starve callers; replace that allocation with a virtual-thread per-task executor
on Java 21+ (use Executors.newVirtualThreadPerTaskExecutor()) or, if supporting
Java <21, switch to an unbounded cached pool (Executors.newCachedThreadPool())
so HealthService.executorService can handle many concurrent tasks without the
6-thread ceiling.
- Around line 469-505: Update the two queries in hasLockWaits and hasSlowQueries
to use performance_schema.processlist instead of the deprecated
INFORMATION_SCHEMA.PROCESSLIST; locate the PreparedStatement constructions
inside methods hasLockWaits(Connection) and hasSlowQueries(Connection) and
perform a direct
s/INFORMATION_SCHEMA.processlist/performance_schema.processlist/ substitution
while preserving the rest of the SQL, parameter binding (stmt.setInt(1, 10)),
query timeout and result handling logic.

@DurgaPrasad-54
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 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.

Copy link
Contributor

@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

♻️ Duplicate comments (3)
src/main/java/com/iemr/tm/service/health/HealthService.java (3)

113-113: Fixed pool of 6 still causes starvation at 4+ concurrent /health callers.

Each /health request consumes 2 executorService slots (MySQL + Redis tasks). With 6 threads, the 4th concurrent caller's tasks queue behind earlier requests, starting the 4-second aggregate deadline while the tasks are still waiting. The pool was bumped from 2 → 6, which helps but doesn't eliminate the problem. The recommended fix is a virtual-thread executor (Java 21+) or an unbounded cached pool:

♻️ Proposed fix
-        this.executorService = Executors.newFixedThreadPool(6);
+        // Java 21+: lightweight, per-request concurrency with no fixed cap
+        this.executorService = Executors.newVirtualThreadPerTaskExecutor();
+        // Java < 21 alternative: Executors.newCachedThreadPool()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` at line 113, The
fixed-size pool in HealthService (executorService created via
Executors.newFixedThreadPool) causes task starvation because each /health
request submits two tasks (MySQL + Redis); replace the fixed pool with an
unbounded/cached or virtual-thread executor so requests don't block: update the
executorService initialization (where Executors.newFixedThreadPool(6) is used)
to use either a virtual-thread-per-task executor
(Executors.newVirtualThreadPerTaskExecutor() on Java 21+) or
Executors.newCachedThreadPool() so each health check’s subtasks can run without
queuing and avoid aggregate-deadline delays.

107-107: Dead code — ADVANCED_HEALTH_CHECKS_ENABLED guard is still unreachable.

private static final boolean ADVANCED_HEALTH_CHECKS_ENABLED = true is a compile-time constant; the if (!ADVANCED_HEALTH_CHECKS_ENABLED) branch on Lines 360–362 can never execute. Remove the constant and the dead branch, or replace it with a runtime/config flag (e.g., read from application.properties) to make the guard meaningful.

♻️ Proposed cleanup
-    // Advanced checks always enabled
-    private static final boolean ADVANCED_HEALTH_CHECKS_ENABLED = true;
     private boolean performAdvancedMySQLChecksWithThrottle() {
-        if (!ADVANCED_HEALTH_CHECKS_ENABLED) {
-            return false; // Advanced checks disabled
-        }
-
         long currentTime = System.currentTimeMillis();

Also applies to: 359-362

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` at line 107, The
class-level compile-time constant ADVANCED_HEALTH_CHECKS_ENABLED in
HealthService makes the runtime guard unreachable; either remove the constant
and delete the dead if-block that checks it (the branch currently unreachable
where the code returns early), or convert the flag to a runtime-config boolean
(e.g., inject via `@Value`("${health.advanced.enabled:false}") or read from
Environment/ApplicationProperties) and replace usages of
ADVANCED_HEALTH_CHECKS_ENABLED with that injected field so the if
(!ADVANCED_HEALTH_CHECKS_ENABLED) branch in HealthService becomes meaningful at
runtime.

471-476: INFORMATION_SCHEMA.PROCESSLIST is deprecated — migrate to performance_schema.processlist.

Both hasLockWaits and hasSlowQueries query INFORMATION_SCHEMA.PROCESSLIST, which is deprecated in MySQL 8 and subject to removal. The recommended replacement is performance_schema.processlist.

♻️ Proposed migration for both methods
-        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST " +
+        "SELECT COUNT(*) FROM performance_schema.processlist " +
         "WHERE (state = 'Waiting for table metadata lock' " +
         "   OR state = 'Waiting for row lock' " +
         "   OR state = 'Waiting for lock') " +
         "AND user = SUBSTRING_INDEX(USER(), '@', 1)")) {
-        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST " +
+        "SELECT COUNT(*) FROM performance_schema.processlist " +
         "WHERE command != 'Sleep' AND time > ? AND user NOT IN ('event_scheduler', 'system user')")) {

Also applies to: 491-493

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 471
- 476, Update both hasLockWaits and hasSlowQueries to use
performance_schema.processlist instead of INFORMATION_SCHEMA.PROCESSLIST by
changing the SQL string(s) to reference performance_schema.processlist (the
column names used—state, user, etc.—are the same), and apply the same
replacement for the duplicate occurrence around the other query (lines noted in
the review). Ensure the PreparedStatement SQL in methods hasLockWaits and
hasSlowQueries uses "performance_schema.processlist" in place of
"INFORMATION_SCHEMA.PROCESSLIST" so the queries work on MySQL 8+.
🧹 Nitpick comments (2)
src/main/java/com/iemr/tm/service/health/HealthService.java (2)

136-138: advancedCheckExecutor is shutdownNow()'d without waiting, unlike executorService.

An in-flight advanced check (up to 500 ms) will be abruptly interrupted with no drain window. For consistency with executorService, apply a brief awaitTermination before escalating to shutdownNow:

♻️ Proposed fix
         if (advancedCheckExecutor != null && !advancedCheckExecutor.isShutdown()) {
-            advancedCheckExecutor.shutdownNow();
+            advancedCheckExecutor.shutdown();
+            try {
+                if (!advancedCheckExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
+                    advancedCheckExecutor.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                advancedCheckExecutor.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 136
- 138, The advancedCheckExecutor is being abruptly shutdown with shutdownNow()
without the graceful wait used for executorService; change the shutdown sequence
for advancedCheckExecutor to mirror executorService by first calling
advancedCheckExecutor.shutdown(), then awaitTermination for a short timeout
(e.g., 500 ms) and only call advancedCheckExecutor.shutdownNow() if
awaitTermination returns false or is interrupted, ensuring you handle
InterruptedException as done for executorService.

558-563: Silent catch in evaluatePoolMetrics swallows JMX attribute errors without any trace.

Add at least a logger.debug so that transient JMX failures are observable in diagnostic logs.

♻️ Proposed fix
         } catch (Exception e) {
-            // Continue to next MBean
+            logger.debug("Could not read pool metrics from MBean {}: {}", objectName, e.getMessage());
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 558
- 563, The catch in evaluatePoolMetrics silently swallows exceptions from JMX
attribute reads; update the catch block to log the exception at debug level so
transient JMX failures are visible. Specifically, inside evaluatePoolMetrics'
catch (Exception e) for the MBean attribute inspection, call logger.debug with a
concise message that includes context (e.g., the MBean name/ObjectName and
attribute being read) and pass the exception (e) so stack/causes are recorded;
keep behavior otherwise unchanged so the method still returns false on error.
🤖 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/tm/service/health/HealthService.java`:
- Around line 219-232: checkMySQLHealthSync currently relies on
dataSource.getConnection() which can block up to the Tomcat JDBC pool's maxWait
(default 30s) and violate the 4s health deadline; set a short max-wait for the
health DataSource so connection acquisition respects the MYSQL_TIMEOUT_SECONDS
(e.g. 3000ms) or use a dedicated DataSource for health checks with that
max-wait. Locate uses of dataSource.getConnection() and
stmt.setQueryTimeout(...) in checkMySQLHealthSync and update configuration (e.g.
spring.datasource.tomcat.max-wait) or create/configure a dedicated health
DataSource with max-wait ~3000 to ensure getConnection() returns or fails within
the health timeout.

---

Duplicate comments:
In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Line 113: The fixed-size pool in HealthService (executorService created via
Executors.newFixedThreadPool) causes task starvation because each /health
request submits two tasks (MySQL + Redis); replace the fixed pool with an
unbounded/cached or virtual-thread executor so requests don't block: update the
executorService initialization (where Executors.newFixedThreadPool(6) is used)
to use either a virtual-thread-per-task executor
(Executors.newVirtualThreadPerTaskExecutor() on Java 21+) or
Executors.newCachedThreadPool() so each health check’s subtasks can run without
queuing and avoid aggregate-deadline delays.
- Line 107: The class-level compile-time constant ADVANCED_HEALTH_CHECKS_ENABLED
in HealthService makes the runtime guard unreachable; either remove the constant
and delete the dead if-block that checks it (the branch currently unreachable
where the code returns early), or convert the flag to a runtime-config boolean
(e.g., inject via `@Value`("${health.advanced.enabled:false}") or read from
Environment/ApplicationProperties) and replace usages of
ADVANCED_HEALTH_CHECKS_ENABLED with that injected field so the if
(!ADVANCED_HEALTH_CHECKS_ENABLED) branch in HealthService becomes meaningful at
runtime.
- Around line 471-476: Update both hasLockWaits and hasSlowQueries to use
performance_schema.processlist instead of INFORMATION_SCHEMA.PROCESSLIST by
changing the SQL string(s) to reference performance_schema.processlist (the
column names used—state, user, etc.—are the same), and apply the same
replacement for the duplicate occurrence around the other query (lines noted in
the review). Ensure the PreparedStatement SQL in methods hasLockWaits and
hasSlowQueries uses "performance_schema.processlist" in place of
"INFORMATION_SCHEMA.PROCESSLIST" so the queries work on MySQL 8+.

---

Nitpick comments:
In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Around line 136-138: The advancedCheckExecutor is being abruptly shutdown with
shutdownNow() without the graceful wait used for executorService; change the
shutdown sequence for advancedCheckExecutor to mirror executorService by first
calling advancedCheckExecutor.shutdown(), then awaitTermination for a short
timeout (e.g., 500 ms) and only call advancedCheckExecutor.shutdownNow() if
awaitTermination returns false or is interrupted, ensuring you handle
InterruptedException as done for executorService.
- Around line 558-563: The catch in evaluatePoolMetrics silently swallows
exceptions from JMX attribute reads; update the catch block to log the exception
at debug level so transient JMX failures are visible. Specifically, inside
evaluatePoolMetrics' catch (Exception e) for the MBean attribute inspection,
call logger.debug with a concise message that includes context (e.g., the MBean
name/ObjectName and attribute being read) and pass the exception (e) so
stack/causes are recorded; keep behavior otherwise unchanged so the method still
returns false on error.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc45761 and aadf406.

📒 Files selected for processing (1)
  • src/main/java/com/iemr/tm/service/health/HealthService.java

@DurgaPrasad-54 DurgaPrasad-54 changed the title feat(healt,version): add health and version endpoints feat(health,version): add health and version endpoints Feb 23, 2026
Copy link
Contributor

@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

♻️ Duplicate comments (4)
src/main/java/com/iemr/tm/service/health/HealthService.java (4)

411-447: INFORMATION_SCHEMA.PROCESSLIST is deprecated in MySQL 8.0 and subject to removal in a future release.

Both hasLockWaits and hasSlowQueries query this table. The recommended replacement is performance_schema.processlist. This was raised in a prior review and remains unaddressed.

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 411
- 447, Change both hasLockWaits and hasSlowQueries to query
performance_schema.processlist instead of INFORMATION_SCHEMA.PROCESSLIST: update
the SQL strings in hasLockWaits and hasSlowQueries to use
performance_schema.processlist (keeping the same WHERE clauses and parameters),
ensure you still set the 2s query timeout and parameters (time parameter in
hasSlowQueries), and keep the same result handling; also update the surrounding
code/comments/log messages if necessary to reflect the new source
(performance_schema.processlist) and verify the DB user has permission to read
performance_schema.

90-90: ADVANCED_HEALTH_CHECKS_ENABLED is a compile-time true constant — the guard at lines 332–334 is unreachable dead code.

This issue was flagged in a prior review and remains unresolved. Either remove the constant and the dead branch or replace it with a runtime/config flag (e.g., read from application properties) so the guard becomes meaningful.

Also applies to: 332-334

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` at line 90, The
ADVANCED_HEALTH_CHECKS_ENABLED constant in HealthService is a compile-time true
which makes the conditional guard around the advanced health-check logic
unreachable; replace it with a runtime-config boolean or remove the dead branch:
either (A) delete the ADVANCED_HEALTH_CHECKS_ENABLED constant and remove the
guarded code path (the advanced health-check block) from the HealthService
class, or (B) make it configurable by adding a private boolean field (e.g.,
advancedHealthChecksEnabled) populated from application properties or env (use
`@Value`("${app.advancedHealthChecks:false}") or a ConfigProperties bean) and
replace all uses of ADVANCED_HEALTH_CHECKS_ENABLED with that field so the guard
around the advanced health-check method(s) becomes effective (ensure to update
any constructors or autowired config to inject the property).

194-211: Both dataSource.getConnection() calls are bounded only by the connection pool's maxWait (default ≈ 30 s), not the 4-second health deadline.

The prior review raised this for checkMySQLHealthSync; performAdvancedMySQLChecksWithThrottle (line 361) has the same exposure. If the DB host drops packets, either call can pin the executor thread well beyond the aggregate deadline. Mitigate by configuring a short maxWait (≤ 3 s) on this DataSource or by using a dedicated health DataSource.

Also applies to: 361-361

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 194
- 211, checkMySQLHealthSync and performAdvancedMySQLChecksWithThrottle currently
call dataSource.getConnection() which can block up to the pool's maxWait (≈30s)
and exceed the 4s health deadline; fix by ensuring connection acquisition is
bounded: either configure the DataSource used by these health checks with a
short maxWait (≤3s) or create and use a dedicated health DataSource (e.g.,
healthDataSource) with aggressive maxWait/connection acquisition settings, then
update checkMySQLHealthSync and performAdvancedMySQLChecksWithThrottle to use
that DataSource so connection acquisition cannot pin the executor beyond the
health deadline.

96-96: Fixed thread pool still risks concurrent-probe starvation.

Increasing the pool from 2 to 6 delays but does not eliminate the issue: three or more concurrent /health probes will still queue tasks and false-DOWN under load. The prior recommendation to switch to a virtual-thread or cached pool remains applicable.

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

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` at line 96, The
fixed-size executorService created in HealthService via
Executors.newFixedThreadPool(6) can still cause probe-task queuing and false
DOWN responses; replace it with a non-bounded/thread-per-task executor such as
Executors.newCachedThreadPool() or, if running on a Java version that supports
Project Loom, Executors.newVirtualThreadPerTaskExecutor() to ensure healthProbe
tasks in HealthService do not block each other; update the executorService
initialization and any shutdown logic accordingly (search for executorService
and the constructor/initialization in HealthService to locate the change).
🧹 Nitpick comments (1)
src/main/java/com/iemr/tm/service/health/HealthService.java (1)

307-308: Redundant || SEVERITY_CRITICAL.equals(severity) in computeOverallStatus.

SEVERITY_CRITICAL is set exclusively when STATUS_DOWN is also set (in determineSeverity, performHealthCheck's catch block, and ensurePopulated). The second condition is never independently true, making the OR misleading.

♻️ Proposed cleanup
-            if (STATUS_DOWN.equals(status) || SEVERITY_CRITICAL.equals(severity)) {
+            if (STATUS_DOWN.equals(status)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/tm/service/health/HealthService.java` around lines 307
- 308, The check in computeOverallStatus uses "if (STATUS_DOWN.equals(status) ||
SEVERITY_CRITICAL.equals(severity))" but SEVERITY_CRITICAL is only ever set when
STATUS_DOWN is set, so remove the redundant "||
SEVERITY_CRITICAL.equals(severity)" term; update computeOverallStatus to only
test STATUS_DOWN.equals(status) (or otherwise consolidate logic) and ensure no
other callers rely on the secondary check; reference computeOverallStatus and
corroborate with determineSeverity, performHealthCheck, and ensurePopulated to
keep behavior consistent.
🤖 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/tm/service/health/HealthService.java`:
- Around line 405-408: The catch block in HealthService (surrounding the call to
performAdvancedMySQLChecks) currently treats any Exception as a degraded state
by returning new AdvancedCheckResult(true); change this to fail-open: log the
exception details (using logger.warn or logger.debug with the exception) and
return new AdvancedCheckResult(false) so transient errors don’t mark the service
DEGRADED for the throttle window; keep references to performAdvancedMySQLChecks
and AdvancedCheckResult so reviewers can find and update the catch block
accordingly.

---

Duplicate comments:
In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Around line 411-447: Change both hasLockWaits and hasSlowQueries to query
performance_schema.processlist instead of INFORMATION_SCHEMA.PROCESSLIST: update
the SQL strings in hasLockWaits and hasSlowQueries to use
performance_schema.processlist (keeping the same WHERE clauses and parameters),
ensure you still set the 2s query timeout and parameters (time parameter in
hasSlowQueries), and keep the same result handling; also update the surrounding
code/comments/log messages if necessary to reflect the new source
(performance_schema.processlist) and verify the DB user has permission to read
performance_schema.
- Line 90: The ADVANCED_HEALTH_CHECKS_ENABLED constant in HealthService is a
compile-time true which makes the conditional guard around the advanced
health-check logic unreachable; replace it with a runtime-config boolean or
remove the dead branch: either (A) delete the ADVANCED_HEALTH_CHECKS_ENABLED
constant and remove the guarded code path (the advanced health-check block) from
the HealthService class, or (B) make it configurable by adding a private boolean
field (e.g., advancedHealthChecksEnabled) populated from application properties
or env (use `@Value`("${app.advancedHealthChecks:false}") or a ConfigProperties
bean) and replace all uses of ADVANCED_HEALTH_CHECKS_ENABLED with that field so
the guard around the advanced health-check method(s) becomes effective (ensure
to update any constructors or autowired config to inject the property).
- Around line 194-211: checkMySQLHealthSync and
performAdvancedMySQLChecksWithThrottle currently call dataSource.getConnection()
which can block up to the pool's maxWait (≈30s) and exceed the 4s health
deadline; fix by ensuring connection acquisition is bounded: either configure
the DataSource used by these health checks with a short maxWait (≤3s) or create
and use a dedicated health DataSource (e.g., healthDataSource) with aggressive
maxWait/connection acquisition settings, then update checkMySQLHealthSync and
performAdvancedMySQLChecksWithThrottle to use that DataSource so connection
acquisition cannot pin the executor beyond the health deadline.
- Line 96: The fixed-size executorService created in HealthService via
Executors.newFixedThreadPool(6) can still cause probe-task queuing and false
DOWN responses; replace it with a non-bounded/thread-per-task executor such as
Executors.newCachedThreadPool() or, if running on a Java version that supports
Project Loom, Executors.newVirtualThreadPerTaskExecutor() to ensure healthProbe
tasks in HealthService do not block each other; update the executorService
initialization and any shutdown logic accordingly (search for executorService
and the constructor/initialization in HealthService to locate the change).

---

Nitpick comments:
In `@src/main/java/com/iemr/tm/service/health/HealthService.java`:
- Around line 307-308: The check in computeOverallStatus uses "if
(STATUS_DOWN.equals(status) || SEVERITY_CRITICAL.equals(severity))" but
SEVERITY_CRITICAL is only ever set when STATUS_DOWN is set, so remove the
redundant "|| SEVERITY_CRITICAL.equals(severity)" term; update
computeOverallStatus to only test STATUS_DOWN.equals(status) (or otherwise
consolidate logic) and ensure no other callers rely on the secondary check;
reference computeOverallStatus and corroborate with determineSeverity,
performHealthCheck, and ensurePopulated to keep behavior consistent.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aadf406 and 8fd4950.

📒 Files selected for processing (1)
  • src/main/java/com/iemr/tm/service/health/HealthService.java

@sonarqubecloud
Copy link

@drtechie drtechie merged commit 0c6c828 into PSMRI:main Mar 2, 2026
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