Skip to content

Conversation

@Suraj-kumar00
Copy link
Contributor

@Suraj-kumar00 Suraj-kumar00 commented Jan 13, 2026

📋 Description

  • Enhanced /health endpoint to show per-component status
  • Added type, host, port, database, version, and response time details
  • Returns error messages and types when components are down
  • Added MongoDB dependency and health check support

Closes #102


✅ Type of Change

  • 🐞 Bug fix (non-breaking change which resolves an issue)
  • New feature (non-breaking change which adds functionality)
  • 🔥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 🛠 Refactor (change that is neither a fix nor a new feature)
  • ⚙️ Config change (configuration file or build script updates)
  • 📚 Documentation (updates to docs or readme)
  • 🧪 Tests (adding new or updating existing tests)
  • 🎨 UI/UX (changes that affect the user interface)
  • 🚀 Performance (improves performance)
  • 🧹 Chore (miscellaneous changes that don't modify src or test files)

ℹ️ Additional Information

Screenshot 2026-01-14 at 12 58 57 AM

Please describe how the changes were tested, and include any relevant screenshots, logs, or other information that provides additional context.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced health status reporting with structured per-component checks for MySQL and Redis.
    • Improved error tracking with detailed error information, response times, and version details.
    • Better handling of missing configurations during health monitoring.

✏️ Tip: You can customize this high-level summary in your review settings.

- Enhanced /health endpoint to show per-component status
- Added type, host, port, database, version, and response time details
- Returns error messages and types when components are down
- Added MongoDB dependency and health check support
@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Restructured health check implementation to introduce per-component health routines with detailed status reporting, version retrieval, and error handling. Replaced HashMap with LinkedHashMap for insertion order preservation, added dedicated MySQL and Redis checks with host/port/database extraction from JDBC URLs, and enriched global payload with timestamps and structured component details.

Changes

Cohort / File(s) Summary
Health Service Refactoring
src/main/java/com/iemr/admin/service/health/HealthService.java
Restructured health check mechanism with per-component checks (MySQL, Redis); added version retrieval for databases; implemented JDBC URL parsing (extractHost, extractPort, extractDatabaseName); introduced HealthCheckResult carrier class; replaced HashMap with LinkedHashMap; added response time logging and enhanced error propagation; integrated configuration injection for database and Redis details.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hop, hop, health checks now run so fine,
Each component in a structured line,
MySQL, Redis dance with glee,
Versions gathered, errors we see,
Timestamps and order, all preserved—
A stronger pulse that is deserved! 🏥

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title claims to add MongoDB health checks, but the actual changes and subsequent commits show MongoDB support was removed from Admin-API, making the title misleading. Update the title to reflect the actual final changes: 'feat: add detailed component health checks for MySQL and Redis' (remove MongoDB reference).
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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 docstrings

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.

@Suraj-kumar00 Suraj-kumar00 changed the title Contributor/suraj feat: add detailed component health checks for MySQL, Redis, and MongoDB Jan 13, 2026
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

🤖 Fix all issues with AI agents
In @src/main/java/com/iemr/admin/service/health/HealthService.java:
- Around line 230-232: The ping check in HealthService uses
pingResult.getDouble("ok") which can throw ClassCastException if "ok" is an
Integer; change the check to safely handle numeric types by retrieving "ok" as a
Number (e.g., pingResult.get("ok", Number.class) or cast to Number) and compare
its doubleValue() to 1.0 (or compare number.intValue() == 1) after null-checking
pingResult; update the condition around mongoTemplate.getDb().runCommand(...) to
use this safe numeric comparison.
🧹 Nitpick comments (3)
src/main/resources/application.properties (1)

78-81: Consider adding MongoDB authentication configuration.

The MongoDB configuration lacks authentication credentials (spring.data.mongodb.username, spring.data.mongodb.password, spring.data.mongodb.authentication-database). While this may be acceptable for the test profile (line 10), ensure that environment-specific property files for production include proper authentication and consider TLS settings (spring.data.mongodb.ssl.enabled).

src/main/java/com/iemr/admin/service/health/HealthService.java (2)

180-184: Consider adding a timeout for Redis operations.

Unlike the MySQL health check which has explicit timeouts, the Redis ping operation doesn't have a timeout configured. If Redis is unresponsive, this could block the health check thread indefinitely. Consider configuring spring.redis.timeout in application properties or wrapping the operation with a timeout.


305-349: Consider extracting common URL parsing logic to reduce duplication.

extractHost and extractPort share identical logic for prefix removal and hostPort extraction (lines 311-316 and 336-341). Consider extracting this to a helper method for maintainability.

♻️ Suggested refactor
+    private String extractHostPort(String jdbcUrl) {
+        if (jdbcUrl == null || "unknown".equals(jdbcUrl)) {
+            return null;
+        }
+        try {
+            String withoutPrefix = jdbcUrl.replaceFirst("jdbc:mysql://", "");
+            int slashIndex = withoutPrefix.indexOf('/');
+            return slashIndex > 0 ? withoutPrefix.substring(0, slashIndex) : withoutPrefix;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
     private String extractHost(String jdbcUrl) {
-        if (jdbcUrl == null || "unknown".equals(jdbcUrl)) {
-            return "unknown";
-        }
-        try {
-            String withoutPrefix = jdbcUrl.replaceFirst("jdbc:mysql://", "");
-            int slashIndex = withoutPrefix.indexOf('/');
-            String hostPort = slashIndex > 0 
-                ? withoutPrefix.substring(0, slashIndex) 
-                : withoutPrefix;
+        String hostPort = extractHostPort(jdbcUrl);
+        if (hostPort == null) return "unknown";
+        try {
             int colonIndex = hostPort.indexOf(':');
             return colonIndex > 0 ? hostPort.substring(0, colonIndex) : hostPort;
         } catch (Exception e) {
-            logger.debug("Could not extract host from URL: {}", e.getMessage());
+            return "unknown";
         }
-        return "unknown";
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c56ce2d and 753bdc0.

📒 Files selected for processing (4)
  • .github/workflows/commit-lint.yml
  • pom.xml
  • src/main/java/com/iemr/admin/service/health/HealthService.java
  • src/main/resources/application.properties
🔇 Additional comments (6)
.github/workflows/commit-lint.yml (1)

20-22: LGTM! Proper base branch fetching for commitlint.

This step correctly fetches the base branch to ensure commitlint --from can access the base SHA. The fetch-depth: 0 in the checkout step combined with this fetch ensures the full commit history is available for comparison.

pom.xml (1)

203-206: LGTM!

The MongoDB starter dependency is correctly added without an explicit version, allowing Spring Boot's dependency management (via spring-boot-starter-parent:3.2.2) to resolve a compatible version.

src/main/java/com/iemr/admin/service/health/HealthService.java (4)

54-77: LGTM! Good use of optional autowiring and configuration defaults.

The @Autowired(required = false) for both RedisTemplate and MongoTemplate correctly handles scenarios where these datastores are not configured. The @Value annotations with sensible defaults ensure the service doesn't fail if properties are missing.


78-114: LGTM! Clean health aggregation logic.

The method correctly aggregates component health statuses and marks overall status as DOWN if any component fails. The null checks for redisTemplate and mongoTemplate properly handle optional datastores.


116-168: LGTM! Robust MySQL health check with proper resource management.

Good implementation with:

  • Try-with-resources for automatic connection and statement cleanup
  • Reasonable timeouts (2s for connection validation, 3s for query)
  • Comprehensive error details including exception type
  • Connection validation before executing the health check query

263-299: LGTM! Defensive version retrieval with graceful fallback.

All three version retrieval methods properly catch exceptions and log at debug level (appropriate since version info is supplementary). Returning null on failure is correctly handled by the callers with null checks before adding to details.

@Suraj-kumar00
Copy link
Contributor Author

Hi @vanitha1822 and @vishwab1
Please review.

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

🤖 Fix all issues with AI agents
In @src/main/java/com/iemr/admin/service/health/HealthService.java:
- Around line 171-178: The MongoDB health check may NPE by unboxing a null from
pingResult.getDouble("ok") and uses fragile equality; update the lambda in
performHealthCheck to first verify pingResult is non-null and contains the "ok"
key, retrieve the value as a Number (e.g., Number okVal = (Number)
pingResult.get("ok")) or via getDouble with null-check, ensure okVal != null,
then use Double.compare(okVal.doubleValue(), 1.0) == 0 or a tolerance (e.g., >=
0.999) to decide success; if the key is missing or the value indicates failure
return new HealthCheckResult(false, null, "Ping returned unexpected or missing
ok value") and keep getMongoDBVersion() and HealthCheckResult usage unchanged.
🧹 Nitpick comments (1)
src/main/java/com/iemr/admin/service/health/HealthService.java (1)

190-215: Response time not recorded for failed health checks.

responseTimeMs is only added when the check succeeds. Recording response time for failures would help diagnose timeout vs immediate failure scenarios.

Proposed improvement
         try {
             HealthCheckResult result = checker.get();
             long responseTime = System.currentTimeMillis() - startTime;
+            details.put("responseTimeMs", responseTime);

             if (result.isHealthy) {
                 logger.debug("{} health check: UP ({}ms)", componentName, responseTime);
                 status.put("status", "UP");
-                details.put("responseTimeMs", responseTime);
                 if (result.version != null) {
                     details.put("version", result.version);
                 }
             } else {
                 logger.warn("{} health check: {}", componentName, result.error);
                 status.put("status", "DOWN");
                 details.put("error", result.error);
             }
             status.put("details", details);
             return status;
         } catch (Exception e) {
+            long responseTime = System.currentTimeMillis() - startTime;
             logger.error("{} health check failed: {}", componentName, e.getMessage());
             status.put("status", "DOWN");
             details.put("error", e.getMessage());
             details.put("errorType", e.getClass().getSimpleName());
+            details.put("responseTimeMs", responseTime);
             status.put("details", details);
             return status;
         }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 753bdc0 and c3dc907.

📒 Files selected for processing (1)
  • src/main/java/com/iemr/admin/service/health/HealthService.java
🔇 Additional comments (3)
src/main/java/com/iemr/admin/service/health/HealthService.java (3)

316-329: LGTM!

Clean internal result holder. If targeting Java 16+, this could be simplified to a record, but the current implementation is perfectly fine.


79-115: Well-structured health aggregation.

Good use of LinkedHashMap for predictable JSON ordering, conditional component checks based on template availability, and clean aggregation of overall status. The timestamp using Instant.now().toString() produces ISO-8601 format which is appropriate for APIs.


260-276: No issue: URL parsing is appropriately MySQL-specific.

The application consistently uses com.mysql.cj.jdbc.Driver across all environment configurations (admin_ci.properties, admin_docker.properties, admin_example.properties). The extractHost, extractPort, and extractDatabaseName methods are correctly hardcoded for the jdbc:mysql:// format that matches the actual JDBC driver in use. The specificity is intentional and appropriate for this codebase's design.

Likely an incorrect or invalid review comment.

Comment on lines 171 to 178
return performHealthCheck("MongoDB", details, () -> {
Document pingResult = mongoTemplate.getDb().runCommand(new Document("ping", 1));
if (pingResult != null && pingResult.getDouble("ok") == 1.0) {
String version = getMongoDBVersion();
return new HealthCheckResult(true, version, null);
}
return new HealthCheckResult(false, null, "Ping returned unexpected response");
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential NullPointerException on MongoDB health check.

Document.getDouble("ok") returns a Double wrapper that can be null if the key doesn't exist. Unboxing a null Double for comparison with 1.0 will throw NPE. Additionally, floating-point equality comparisons are fragile.

Proposed fix
         return performHealthCheck("MongoDB", details, () -> {
             Document pingResult = mongoTemplate.getDb().runCommand(new Document("ping", 1));
-            if (pingResult != null && pingResult.getDouble("ok") == 1.0) {
+            if (pingResult != null) {
+                Double okValue = pingResult.getDouble("ok");
+                if (okValue != null && okValue >= 1.0) {
                     String version = getMongoDBVersion();
                     return new HealthCheckResult(true, version, null);
+                }
             }
             return new HealthCheckResult(false, null, "Ping returned unexpected response");
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return performHealthCheck("MongoDB", details, () -> {
Document pingResult = mongoTemplate.getDb().runCommand(new Document("ping", 1));
if (pingResult != null && pingResult.getDouble("ok") == 1.0) {
String version = getMongoDBVersion();
return new HealthCheckResult(true, version, null);
}
return new HealthCheckResult(false, null, "Ping returned unexpected response");
});
return performHealthCheck("MongoDB", details, () -> {
Document pingResult = mongoTemplate.getDb().runCommand(new Document("ping", 1));
if (pingResult != null) {
Double okValue = pingResult.getDouble("ok");
if (okValue != null && okValue >= 1.0) {
String version = getMongoDBVersion();
return new HealthCheckResult(true, version, null);
}
}
return new HealthCheckResult(false, null, "Ping returned unexpected response");
});
🤖 Prompt for AI Agents
In @src/main/java/com/iemr/admin/service/health/HealthService.java around lines
171 - 178, The MongoDB health check may NPE by unboxing a null from
pingResult.getDouble("ok") and uses fragile equality; update the lambda in
performHealthCheck to first verify pingResult is non-null and contains the "ok"
key, retrieve the value as a Number (e.g., Number okVal = (Number)
pingResult.get("ok")) or via getDouble with null-check, ensure okVal != null,
then use Double.compare(okVal.doubleValue(), 1.0) == 0 or a tolerance (e.g., >=
0.999) to decide success; if the key is missing or the value indicates failure
return new HealthCheckResult(false, null, "Ping returned unexpected or missing
ok value") and keep getMongoDBVersion() and HealthCheckResult usage unchanged.

@drtechie
Copy link
Member

Admin-API doesn't talk to Mongo directly. This is a dependency for FHIR-API.
We can skip the check. Some deployments run without Mongo. They are going to show false negative health.

@Suraj-kumar00 can you also show the final JSON output?

- Removed MongoDB dependency and configuration (Admin-API doesn't use MongoDB)
- Removed MongoDB health check from HealthService
- Removed unnecessary base branch fetch step from commit-lint workflow
- MongoDB is only used by FHIR-API, not Admin-API
@sonarqubecloud
Copy link

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

🤖 Fix all issues with AI agents
In `@src/main/java/com/iemr/admin/service/health/HealthService.java`:
- Around line 146-177: In performHealthCheck, stop returning raw exception
messages and ensure failure payloads are consistent: replace uses of
e.getMessage() in the catch and in unhealthy branches with a sanitized error
string (e.g., "InternalError" or a curated mapping) while logging the full
exception internally via logger.error with the exception object; always populate
responseTimeMs (compute using startTime) and errorType (use
e.getClass().getSimpleName() or set to the HealthCheckResult error type) into
details for both checker-failure (result.isHealthy == false) and catch paths so
every returned status map includes status, details.responseTimeMs and
details.errorType alongside a safe details.error value; keep HealthCheckResult
handling (version) unchanged but ensure you log full exception for debugging
only and return only sanitized error text to callers.

Comment on lines +146 to 177
private Map<String, Object> performHealthCheck(String componentName,
Map<String, Object> details,
Supplier<HealthCheckResult> checker) {
Map<String, Object> status = new LinkedHashMap<>();
long startTime = System.currentTimeMillis();

try {
HealthCheckResult result = checker.get();
long responseTime = System.currentTimeMillis() - startTime;

if (result.isHealthy) {
logger.debug("{} health check: UP ({}ms)", componentName, responseTime);
status.put("status", "UP");
details.put("responseTimeMs", responseTime);
if (result.version != null) {
details.put("version", result.version);
}
} else {
logger.warn("{} health check: {}", componentName, result.error);
status.put("status", "DOWN");
details.put("error", result.error);
}
logger.warn("Database health check: Connection not valid");
return false;
status.put("details", details);
return status;
} catch (Exception e) {
logger.error("Database health check failed: {}", e.getMessage());
return false;
logger.error("{} health check failed: {}", componentName, e.getMessage());
status.put("status", "DOWN");
details.put("error", e.getMessage());
details.put("errorType", e.getClass().getSimpleName());
status.put("details", details);
return status;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t return raw exception messages; ensure failure payloads are consistent.
e.getMessage() can leak internal host/user details to callers. Also, responseTimeMs and errorType aren’t guaranteed on all failure paths. Consider sanitizing errors and always attaching timing/error metadata.

🐛 Proposed fix
         try {
             HealthCheckResult result = checker.get();
             long responseTime = System.currentTimeMillis() - startTime;
+            details.put("responseTimeMs", responseTime);
 
             if (result.isHealthy) {
                 logger.debug("{} health check: UP ({}ms)", componentName, responseTime);
                 status.put("status", "UP");
-                details.put("responseTimeMs", responseTime);
                 if (result.version != null) {
                     details.put("version", result.version);
                 }
             } else {
                 logger.warn("{} health check: {}", componentName, result.error);
                 status.put("status", "DOWN");
-                details.put("error", result.error);
+                details.put("error", result.error != null ? result.error : "Health check failed");
+                details.put("errorType", "CheckFailed");
             }
             status.put("details", details);
             return status;
         } catch (Exception e) {
-            logger.error("{} health check failed: {}", componentName, e.getMessage());
+            long responseTime = System.currentTimeMillis() - startTime;
+            logger.error("{} health check failed", componentName, e);
             status.put("status", "DOWN");
-            details.put("error", e.getMessage());
+            details.put("responseTimeMs", responseTime);
+            details.put("error", "Health check failed");
             details.put("errorType", e.getClass().getSimpleName());
             status.put("details", details);
             return status;
         }
🤖 Prompt for AI Agents
In `@src/main/java/com/iemr/admin/service/health/HealthService.java` around lines
146 - 177, In performHealthCheck, stop returning raw exception messages and
ensure failure payloads are consistent: replace uses of e.getMessage() in the
catch and in unhealthy branches with a sanitized error string (e.g.,
"InternalError" or a curated mapping) while logging the full exception
internally via logger.error with the exception object; always populate
responseTimeMs (compute using startTime) and errorType (use
e.getClass().getSimpleName() or set to the HealthCheckResult error type) into
details for both checker-failure (result.isHealthy == false) and catch paths so
every returned status map includes status, details.responseTimeMs and
details.errorType alongside a safe details.error value; keep HealthCheckResult
handling (version) unchanged but ensure you log full exception for debugging
only and return only sanitized error text to callers.

@Suraj-kumar00
Copy link
Contributor Author

Suraj-kumar00 commented Jan 17, 2026

Admin-API doesn't talk to Mongo directly. This is a dependency for FHIR-API. We can skip the check. Some deployments run without Mongo. They are going to show false negative health.

@Suraj-kumar00 can you also show the final JSON output?

@drtechie, after applying the fixes you suggested, here is the refined JSON for /health:

{
  "status": "DOWN",
  "timestamp": "2026-01-17T12:11:27.086571048Z",
  "components": {
    "mysql": {
      "status": "DOWN",
      "details": {
        "type": "MySQL",
        "host": "mysql-container",
        "port": "3306",
        "database": "db_iemr",
        "error": "java.sql.SQLException: Access denied for user 'root'@'172.18.0.5' (using password: YES)",
        "errorType": "RuntimeException"
      }
    },
    "redis": {
      "status": "UP",
      "details": {
        "type": "Redis",
        "host": "redis-container",
        "port": 6379,
        "responseTimeMs": 446,
        "version": "7.2.4"
      }
    }
  }
}

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.

[C4GT Community]: Implement /health and /version Endpoints Across All API Repositories

2 participants