Skip to content

Conversation

Suraj-kumar00
Copy link
Contributor

@Suraj-kumar00 Suraj-kumar00 commented Aug 1, 2025

📋 Description

JIRA ID:
This PR resolve issue

This PR implements both the /health and /version endpoints for the Admin API service as requested in issue #102

What has been implemented:

/health Endpoint:

  • Added a new /health endpoint that returns a minimal response with status "UP"
  • Made the endpoint lightweight for fast response time
  • No authentication required for monitoring tools to access
  • Follows the standard health check pattern for services

/version Endpoint:

  • Implemented a detailed /version endpoint that returns:
    • Git commit hash (git.commit.id)
    • Git build timestamp (git.build.time)
    • Application version (build.version)
    • Build timestamp (build.time)
    • Current time in IST format

✅ 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

Tested in Docker environment

Screenshot 2025-08-01 at 9 35 25 PM Screenshot 2025-08-01 at 9 01 23 PM

Summary by CodeRabbit

  • New Features

    • Health endpoint reporting overall status plus detailed DB and Redis checks.
    • Version endpoint returning structured JSON version/build info.
    • Centralized JWT user‑ID validation applied to all endpoints (explicitly skipped for health and version).
  • Refactor

    • Version endpoint now delegates retrieval to a dedicated version service.
  • Chores

    • Build tooling updated to produce richer git/build metadata during the build.

Copy link

coderabbitai bot commented Aug 1, 2025

Walkthrough

Adds health and version services/endpoints, a security filter registration, updates JWT filter to skip health/version endpoints, and updates Maven plugins to emit git/build metadata for runtime consumption.

Changes

Cohort / File(s) Change Summary
Maven Build Metadata Plugins
pom.xml
Upgrades git-commit-id-plugin to v4.9.10; binds revision goal to initialize; generates git.properties (full commit id, build time) in properties format; adds build-info execution to spring-boot-maven-plugin.
Security Filter Configuration
src/main/java/com/iemr/admin/config/SecurityFilterConfig.java
New Spring config registering JwtUserIdValidationFilter as a servlet filter bean for /* with order 1 and injected dependencies (JwtAuthenticationUtil and allowed origins).
JWT Filter Update
src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java
Early-exit to skip JWT/CORS processing for /health and /version (including context-path variants); moved path retrieval earlier; improved error message content and removed redundant error handling.
Health Controller & Service
src/main/java/com/iemr/admin/controller/health/HealthController.java,
src/main/java/com/iemr/admin/service/health/HealthService.java
New /health REST controller and HealthService. Service aggregates DB and optional Redis checks (ping + simple set/get/delete), timestamps, app version; returns detailed component statuses and overall UP/DOWN.
Version Controller & Service
src/main/java/com/iemr/admin/controller/version/VersionController.java,
src/main/java/com/iemr/admin/service/version/VersionService.java
VersionController refactored to use new VersionService which reads git.properties, META-INF/build-info.properties, and fallback pom properties to produce JSON version info including IST timestamp; controller now explicitly produces JSON.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant JwtFilter as JwtUserIdValidationFilter
    participant HealthCtrl as HealthController
    participant HealthSvc as HealthService
    participant VersionCtrl as VersionController
    participant VersionSvc as VersionService

    Client->>API: GET /health
    API->>JwtFilter: filter request
    JwtFilter-->>API: skip validation for /health
    API->>HealthCtrl: health()
    HealthCtrl->>HealthSvc: checkHealth()
    HealthSvc-->>HealthCtrl: health map
    HealthCtrl-->>API: 200/503 JSON
    API-->>Client: response

    Client->>API: GET /version
    API->>JwtFilter: filter request
    JwtFilter-->>API: skip validation for /version
    API->>VersionCtrl: versionInformation()
    VersionCtrl->>VersionSvc: getVersionInformation()
    VersionSvc-->>VersionCtrl: JSON string
    VersionCtrl-->>API: 200 JSON
    API-->>Client: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Possibly related PRs

Suggested reviewers

  • drtechie

Poem

"I nibble logs and hop through code so spry,
Health checks hum, version stars in the sky.
Filters skip the paths that need a rest,
Git and build whispers tucked in a nest.
Hop on, reviewers — let's make this one fly! 🐇"

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🔭 Outside diff range comments (1)
src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (1)

165-170: Remove duplicate code

Lines 168-169 are exact duplicates of lines 165-166.

 			logger.warn("No valid authentication token found");
 			response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token");
-
-			logger.warn("No valid authentication token found");
-			response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token");
🧹 Nitpick comments (1)
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)

31-40: Consider adding OpenAPI documentation

For consistency with other endpoints like /version, consider adding Swagger/OpenAPI documentation.

+import io.swagger.v3.oas.annotations.Operation;
+
 @RestController
 public class HealthController {
 
+    @Operation(summary = "Health check endpoint")
     @GetMapping("/health")
     public ResponseEntity<Map<String, String>> health() {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3313c7b and e71943a.

📒 Files selected for processing (5)
  • pom.xml (1 hunks)
  • src/main/java/com/iemr/admin/config/SecurityFilterConfig.java (1 hunks)
  • src/main/java/com/iemr/admin/controller/health/HealthController.java (1 hunks)
  • src/main/java/com/iemr/admin/controller/version/VersionController.java (2 hunks)
  • src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
src/main/java/com/iemr/admin/controller/version/VersionController.java (1)
  • RestController (43-160)
src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (1)
src/main/java/com/iemr/admin/utils/http/AuthorizationHeaderRequestWrapper.java (1)
  • AuthorizationHeaderRequestWrapper (10-42)
🔇 Additional comments (6)
pom.xml (1)

489-500: LGTM!

The addition of the build-info goal correctly generates build metadata that the /version endpoint can consume.

src/main/java/com/iemr/admin/config/SecurityFilterConfig.java (1)

12-32: LGTM!

Clean Spring configuration that properly registers the JWT filter with URL exclusions.

src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (2)

24-48: LGTM!

Proper Spring integration with no-args constructor and FilterConfig storage.


224-236: LGTM!

Proper Spring setter injection with appropriate null handling.

src/main/java/com/iemr/admin/controller/version/VersionController.java (2)

48-64: LGTM!

Proper JSON content type declaration and structured response handling.


137-160: LGTM!

Well-implemented helper methods with proper resource management and error handling.

Comment on lines 66 to 136
private String readGitPropertiesAsJson() throws Exception {
StringBuilder json = new StringBuilder();
json.append("{\n");

// Read Git properties
Properties gitProps = loadPropertiesFile("git.properties");
if (gitProps != null) {
// For git.commit.id, look for both standard and abbrev versions
String commitId = gitProps.getProperty("git.commit.id", null);
if (commitId == null) {
commitId = gitProps.getProperty("git.commit.id.abbrev", "unknown");
}
json.append(" \"git.commit.id\": \"").append(commitId).append("\",\n");

// For git.build.time, look for various possible property names
String buildTime = gitProps.getProperty("git.build.time", null);
if (buildTime == null) {
buildTime = gitProps.getProperty("git.commit.time", null);
}
if (buildTime == null) {
buildTime = gitProps.getProperty("git.commit.timestamp", "unknown");
}
json.append(" \"git.build.time\": \"").append(buildTime).append("\",\n");
} else {
logger.warn("git.properties file not found. Git information will be unavailable.");
json.append(" \"git.commit.id\": \"information unavailable\",\n");
json.append(" \"git.build.time\": \"information unavailable\",\n");
}

// Read build properties if available
Properties buildProps = loadPropertiesFile("META-INF/build-info.properties");
if (buildProps != null) {
// Extract version - checking for both standard and nested formats
String version = buildProps.getProperty("build.version", null);
if (version == null) {
version = buildProps.getProperty("build.version.number", null);
}
if (version == null) {
version = buildProps.getProperty("version", "unknown");
}
json.append(" \"build.version\": \"").append(version).append("\",\n");

// Extract time - checking for both standard and alternate formats
String time = buildProps.getProperty("build.time", null);
if (time == null) {
time = buildProps.getProperty("build.timestamp", null);
}
if (time == null) {
time = buildProps.getProperty("timestamp", "unknown");
}
json.append(" \"build.time\": \"").append(time).append("\",\n");
} else {
logger.info("build-info.properties not found, trying Maven properties");
// Fallback to maven project version
Properties mavenProps = loadPropertiesFile("META-INF/maven/com.iemr.admin/admin-api/pom.properties");
if (mavenProps != null) {
String version = mavenProps.getProperty("version", "unknown");
json.append(" \"build.version\": \"").append(version).append("\",\n");
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n");
} else {
logger.warn("Neither build-info.properties nor Maven properties found.");
json.append(" \"build.version\": \"3.1.0\",\n"); // Default version
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n");
}
}
json.append(" \"current.time\": \"").append(getCurrentIstTimeFormatted()).append("\"\n");

json.append(" }");
return json.toString();
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using proper JSON serialization

Manual JSON construction with StringBuilder is error-prone. Property values containing special characters (quotes, newlines) will break the JSON format.

Consider using Jackson (already in classpath) or creating a POJO:

private String readGitPropertiesAsJson() throws Exception {
    Map<String, String> versionInfo = new LinkedHashMap<>();
    
    // Read Git properties
    Properties gitProps = loadPropertiesFile("git.properties");
    if (gitProps != null) {
        String commitId = gitProps.getProperty("git.commit.id", 
            gitProps.getProperty("git.commit.id.abbrev", "unknown"));
        versionInfo.put("git.commit.id", commitId);
        
        String buildTime = gitProps.getProperty("git.build.time",
            gitProps.getProperty("git.commit.time",
                gitProps.getProperty("git.commit.timestamp", "unknown")));
        versionInfo.put("git.build.time", buildTime);
    } else {
        logger.warn("git.properties file not found. Git information will be unavailable.");
        versionInfo.put("git.commit.id", "information unavailable");
        versionInfo.put("git.build.time", "information unavailable");
    }
    
    // ... similar for other properties ...
    
    versionInfo.put("current.time", getCurrentIstTimeFormatted());
    
    // Use ObjectMapper to properly serialize
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(versionInfo);
}
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/controller/version/VersionController.java lines
66 to 136, the method readGitPropertiesAsJson manually builds a JSON string
using StringBuilder, which risks breaking JSON format if property values contain
special characters. To fix this, replace the manual string construction with a
Map to hold the properties and use Jackson's ObjectMapper to serialize the Map
to a JSON string. This ensures proper escaping and formatting of all values.
Populate the Map with all required properties as before, then return the JSON
string by calling ObjectMapper.writeValueAsString on the Map.

Comment on lines 74 to 82
// Check for excluded URLs first
String path = request.getRequestURI();
if (isExcludedUrl(path) ||
path.equals("/health") ||
path.equals("/version")) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove hardcoded URL exclusions

The /health and /version paths are already configured as excluded URLs in SecurityFilterConfig. Remove the hardcoded checks to follow DRY principle.

 		// Check for excluded URLs first
 		String path = request.getRequestURI();
-		if (isExcludedUrl(path) || 
-		    path.equals("/health") ||
-		    path.equals("/version")) {
+		if (isExcludedUrl(path)) {
 			filterChain.doFilter(servletRequest, servletResponse);
 			return;
 		}
📝 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
// Check for excluded URLs first
String path = request.getRequestURI();
if (isExcludedUrl(path) ||
path.equals("/health") ||
path.equals("/version")) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// Check for excluded URLs first
String path = request.getRequestURI();
if (isExcludedUrl(path)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java between
lines 74 and 82, remove the hardcoded checks for "/health" and "/version" URL
paths since these are already included in the excluded URLs configured in
SecurityFilterConfig. Keep only the call to isExcludedUrl(path) to check for
exclusions, ensuring no duplication and adherence to the DRY principle.

@GetMapping("/health")
public ResponseEntity<Map<String, String>> health() {
Map<String, String> response = new HashMap<>();
response.put("status", "UP");
Copy link
Member

Choose a reason for hiding this comment

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

This alone won't be enough.
Can we test associated services like DB and Redis connection?

@drtechie
Copy link
Member

drtechie commented Aug 1, 2025

@vishwab1 / @vanitha1822
Please review.

json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n");
} else {
logger.warn("Neither build-info.properties nor Maven properties found.");
json.append(" \"build.version\": \"3.1.0\",\n"); // Default version
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid Hardcoding Version

Copy link
Contributor

@vishwab1 vishwab1 Aug 1, 2025

Choose a reason for hiding this comment

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

We can move the logic (like reading Git properties and building the JSON) into a separate service method instead of writing everything inside the controller. Also, let’s review all the suggestions given by CodeRabbit once again. One of the points to check is the use of StringBuilder, as it might cause issues in some cases.

@Suraj-kumar00 Suraj-kumar00 requested a review from drtechie August 7, 2025 05:43
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: 3

🧹 Nitpick comments (3)
src/main/java/com/iemr/admin/service/version/VersionService.java (2)

45-46: Consider using @ConfigurationProperties for better configuration management.

Using @Value for individual properties works but @ConfigurationProperties would provide better type safety and validation for application properties.


107-107: Hardcoded Maven path may not be portable across different projects.

The hardcoded path META-INF/maven/com.iemr.admin/admin-api/pom.properties is specific to this project's group/artifact ID and may break if the project structure changes.

Consider making this configurable or using a more generic approach:

-            Properties mavenProps = loadPropertiesFile("META-INF/maven/com.iemr.admin/admin-api/pom.properties");
+            Properties mavenProps = loadPropertiesFile("META-INF/maven/" + 
+                System.getProperty("project.groupId", "com.iemr.admin") + "/" +
+                System.getProperty("project.artifactId", "admin-api") + "/pom.properties");
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)

54-55: Consider using constants for status values.

The string literal "UP" is used for comparison. Consider defining constants to avoid magic strings and ensure consistency across the codebase.

+    private static final String STATUS_UP = "UP";
+    private static final String STATUS_DOWN = "DOWN";
+
     // Return 503 if any service is down, 200 if all are up
     String status = (String) healthStatus.get("status");
-    HttpStatus httpStatus = "UP".equals(status) ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE;
+    HttpStatus httpStatus = STATUS_UP.equals(status) ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e71943a and 50bbaa0.

📒 Files selected for processing (7)
  • pom.xml (3 hunks)
  • src/main/java/com/iemr/admin/config/SecurityFilterConfig.java (1 hunks)
  • src/main/java/com/iemr/admin/controller/health/HealthController.java (1 hunks)
  • src/main/java/com/iemr/admin/controller/version/VersionController.java (1 hunks)
  • src/main/java/com/iemr/admin/service/health/HealthService.java (1 hunks)
  • src/main/java/com/iemr/admin/service/version/VersionService.java (1 hunks)
  • src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/iemr/admin/config/SecurityFilterConfig.java
  • src/main/java/com/iemr/admin/controller/version/VersionController.java
  • pom.xml
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
src/main/java/com/iemr/admin/controller/version/VersionController.java (1)
  • RestController (37-60)
src/main/java/com/iemr/admin/service/version/VersionService.java (1)
src/main/java/com/iemr/admin/service/health/HealthService.java (1)
  • Service (39-198)
🔇 Additional comments (8)
src/main/java/com/iemr/admin/service/version/VersionService.java (4)

50-58: LGTM!

Good error handling with graceful fallback to error response when version information cannot be built.


75-91: Robust fallback logic for Git information.

The multiple fallback property keys ensure compatibility with different git-commit-id-plugin configurations, and the graceful handling of missing files with warning logs is well-implemented.


126-138: Proper resource management with try-with-resources.

Good use of try-with-resources for automatic resource cleanup and appropriate exception handling with logging.


140-153: Comprehensive error response handling.

The nested try-catch ensures that even if JSON serialization of the error response fails, a basic JSON error message is returned. This prevents the endpoint from returning non-JSON responses.

src/main/java/com/iemr/admin/controller/health/HealthController.java (1)

46-59: Addresses previous review feedback with comprehensive health checks.

The implementation now includes database and Redis connection testing through the HealthService, directly addressing the previous comment about testing associated services. The HTTP status code mapping (200 for UP, 503 for DOWN) follows health check best practices.

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

87-134: Comprehensive database health check implementation.

Excellent implementation that tests connection validity, executes a simple query, collects metadata, measures response time, and provides detailed error information. The try-with-resources ensures proper connection cleanup.


147-153: Redis operations test provides thorough validation.

The set/get/delete test ensures Redis is not only reachable but also functional for actual operations. Good cleanup of test data to avoid pollution.


189-197: Proper security practice with URL sanitization.

Good security implementation to prevent password exposure in logs and responses by sanitizing database URLs.

Comment on lines +48 to +49
@Autowired(required = false)
private RedisTemplate<String, Object> redisTemplate;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify RedisTemplate configuration and error handling.

Ensure that the RedisTemplate<String, Object> bean is properly configured in the Spring context and that connection pool settings are appropriate for health checks.


🏁 Script executed:

#!/bin/bash
# Description: Check for RedisTemplate configuration and usage patterns

# Search for RedisTemplate configuration
ast-grep --pattern $'@Bean
$$$
RedisTemplate$_($$$) {
  $$$
}'

# Search for Redis configuration files
fd -e properties -e yml -e yaml | xargs grep -l -i redis

Length of output: 296


I’d like to confirm whether Spring Boot’s Redis auto-configuration is on the classpath (i.e. that the spring-boot-starter-data-redis dependency is present and will supply a RedisTemplate bean). Checking your Maven config next:


🏁 Script executed:

#!/bin/bash
# Check for Spring Data Redis starter dependency
grep -R "spring-boot-starter-data-redis" -n .

Length of output: 118


Let’s verify how redisTemplate is used in your health check and confirm your Redis settings:


🏁 Script executed:

#!/bin/bash
# 1. Show redisTemplate usage in HealthService
grep -R "redisTemplate" -n -A3 -B3 src/main/java/com/iemr/admin/service/health/HealthService.java

# 2. List all spring.redis properties in your env and resource files
grep -R "spring.redis" -n src/main/environment src/main/resources/application.properties

# 3. Check for any custom RedisConnectionFactory beans
grep -R "RedisConnectionFactory" -n src/main/java

Length of output: 2553


Let’s confirm the actual @Bean definitions for your Redis templates and check if any connection‐pool settings are configured:


🏁 Script executed:

#!/bin/bash
# 1. Inspect @Bean methods in RoleMasterApplication and RedisConfig
grep -R "@Bean" -n -A3 -B3 src/main/java/com/iemr/admin/RoleMasterApplication.java src/main/java/com/iemr/admin/config/RedisConfig.java

# 2. Look for Lettuce/Jedis pool properties
grep -R "spring.redis.*pool" -n src/main/environment src/main/resources/application.properties

Length of output: 2890


Action Required: Disambiguate RedisTemplate beans & configure connection pooling

It looks like you’ve defined two @Bean methods both named redisTemplate (one in RoleMasterApplication, one in RedisConfig), and you haven’t added any pool settings for Lettuce/Jedis. To avoid startup errors and ensure robust health checks, please:

  • Rename or qualify your template beans so only the intended RedisTemplate<String,Object> is injected into HealthService.
  • Add connection‐pool properties (e.g. spring.redis.lettuce.pool.max-active, max-idle, max-wait or equivalent Jedis settings) in your application*.properties or environment files.
  • Enhance checkRedisHealth() to catch and report any Redis exceptions (e.g. connection timeouts, IO errors) as service DOWN.
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
48-49, the RedisTemplate injection is ambiguous due to multiple beans named
redisTemplate. To fix this, use @Qualifier to specify which RedisTemplate bean
to inject or rename the beans to have distinct names. Additionally, configure
connection pooling properties for Lettuce or Jedis in your application
properties files to improve connection management. Finally, update the
checkRedisHealth() method to catch Redis-related exceptions and mark the service
as DOWN when such exceptions occur.

@Suraj-kumar00
Copy link
Contributor Author

Screenshot 2025-08-07 at 11 17 07 AM Screenshot 2025-08-07 at 11 17 16 AM

@drtechie
Copy link
Member

drtechie commented Aug 8, 2025

Mostly LGTM.
@Suraj-kumar00 can you look into one remaining comment from code rabbit?

cc: @vishwab1

dbStatus.put("status", "UP");
dbStatus.put("database", connection.getMetaData().getDatabaseProductName());
dbStatus.put("driver", connection.getMetaData().getDriverName());
dbStatus.put("url", sanitizeUrl(connection.getMetaData().getURL()));
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to expose this to public.

Copy link

@Suraj-kumar00
Copy link
Contributor Author

Screenshot 2025-08-12 at 10 10 45 PM

@Suraj-kumar00 Suraj-kumar00 requested a review from drtechie August 12, 2025 16:46
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: 4

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

53-55: Fix ambiguous RedisTemplate injection; prefer StringRedisTemplate to avoid NoUniqueBeanDefinitionException

With multiple RedisTemplate beans in the context, field autowiring by type remains ambiguous even with required=false. Inject StringRedisTemplate (auto-configured and unique) to eliminate ambiguity and align with String value operations used in your check.

Apply this diff:

-    @Autowired(required = false)
-    private RedisTemplate<String, Object> redisTemplate;
+    @Autowired(required = false)
+    private StringRedisTemplate stringRedisTemplate;

And add the missing import (see import diff below).

🧹 Nitpick comments (5)
src/main/java/com/iemr/admin/service/health/HealthService.java (5)

24-31: Optional: import Duration instead of using FQCN

Only needed if you adopt the TTL change above.

 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.time.Instant;
+import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;

88-89: Reduce log verbosity for frequently-polled endpoint

Health endpoints are often polled. Logging at INFO on every call can flood logs. Switch to DEBUG.

-        logger.info("Health check completed - Overall status: {}", overallHealth ? "UP" : "DOWN");
+        logger.debug("Health check completed - Overall status: {}", overallHealth ? "UP" : "DOWN");

56-90: Separate liveness and readiness; avoid exposing detailed internals on public /health

Current payload includes DB/Redis details. PR objective mentions a lightweight “status: UP” health endpoint; also, a previous comment said “We don't need to expose this to public.” Consider:

  • Keep /health minimal (liveness): only status and timestamp.
  • Provide /health/ready or a secured endpoint for detailed checks (DB/Redis).
  • Or gate inclusion of the "services" map behind a property.

If you want a quick toggle via property, apply:

     @Value("${app.version:unknown}")
     private String appVersion;
 
+    @Value("${health.details.enabled:false}")
+    private boolean healthDetailsEnabled;

and:

-        healthStatus.put("services", services);
+        if (healthDetailsEnabled) {
+            healthStatus.put("services", services);
+        }

Please confirm whether /health is intended to be publicly accessible and if detailed service statuses should be suppressed by default. I can follow up with a small controller change to split endpoints if needed.


41-46: Consider leveraging Spring Boot Actuator HealthIndicators

Spring Boot Actuator provides production-grade /actuator/health with DB and Redis indicators, standardized status codes, and config-driven detail toggles. Migrating reduces custom code and maintenance.

If Actuator is already on the classpath, you can:

  • Expose health endpoint and include Redis/DB indicators via properties.
  • Optionally add a custom HealthIndicator if extra checks are still needed.

47-55: Prefer constructor injection over field injection

Constructor injection improves immutability, makes dependencies explicit, and simplifies testing.

I can provide a small refactor to constructor-based injection if you’d like.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a373482 and bded299.

📒 Files selected for processing (1)
  • src/main/java/com/iemr/admin/service/health/HealthService.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/iemr/admin/service/health/HealthService.java (1)
src/main/java/com/iemr/admin/service/version/VersionService.java (1)
  • Service (40-154)
🔇 Additional comments (2)
src/main/java/com/iemr/admin/service/health/HealthService.java (2)

142-146: Use StringRedisTemplate for ping execution

Ensure consistency with the injected bean and avoid serializer surprises.

-            String pong = redisTemplate.execute((RedisCallback<String>) connection -> {
-                return connection.ping();
-            });
+            String pong = stringRedisTemplate.execute((RedisCallback<String>) connection -> connection.ping());

Likely an incorrect or invalid review comment.


37-39: Add missing import for StringRedisTemplate

Required for the suggested change to inject the dedicated string template.

 import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;

Likely an incorrect or invalid review comment.

Comment on lines +69 to +75
if (redisTemplate != null) {
Map<String, Object> redisStatus = checkRedisHealth();
services.put("redis", redisStatus);
if (!"UP".equals(redisStatus.get("status"))) {
overallHealth = false;
}
} else {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update null-check to use StringRedisTemplate

Continue the variable rename to the string template to avoid NPEs and mismatches.

-        if (redisTemplate != null) {
+        if (stringRedisTemplate != null) {
             Map<String, Object> redisStatus = checkRedisHealth();
             services.put("redis", redisStatus);
             if (!"UP".equals(redisStatus.get("status"))) {
                 overallHealth = false;
             }
         } else {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
69 to 75, the null-check and subsequent usage still reference redisTemplate
instead of the renamed stringRedisTemplate which can cause NPEs/mismatches;
update the conditional and any calls to use stringRedisTemplate (e.g., if
(stringRedisTemplate != null) { Map<String,Object> redisStatus =
checkRedisHealth(); ... }) and ensure checkRedisHealth and related
methods/fields accept and use stringRedisTemplate consistently.

Comment on lines +97 to +104
// Test connection validity
boolean isConnectionValid = connection.isValid(5); // 5 second timeout

if (isConnectionValid) {
// Execute a simple query to ensure database is responsive
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY);
ResultSet rs = stmt.executeQuery()) {

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Shorten DB isValid timeout and add JDBC query timeout

Prevent the health check from hanging on slow DBs and bound the query execution time.

-            boolean isConnectionValid = connection.isValid(5); // 5 second timeout
+            boolean isConnectionValid = connection.isValid(2); // 2 second timeout
             
             if (isConnectionValid) {
                 // Execute a simple query to ensure database is responsive
-                try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY);
-                     ResultSet rs = stmt.executeQuery()) {
+                try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY)) {
+                    stmt.setQueryTimeout(3); // seconds
+                    try (ResultSet rs = stmt.executeQuery()) {
                     
                     if (rs.next() && rs.getInt(1) == 1) {
                         long responseTime = System.currentTimeMillis() - startTime;
                         
                         dbStatus.put("status", "UP");
                         dbStatus.put("responseTime", responseTime + "ms");
                         dbStatus.put("message", "Database connection successful");
                         
                         logger.debug("Database health check: UP ({}ms)", responseTime);
                     } else {
                         dbStatus.put("status", "DOWN");
                         dbStatus.put("message", "Database query returned unexpected result");
                         logger.warn("Database health check: Query returned unexpected result");
                     }
-                }
+                    }
+                }
📝 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
// Test connection validity
boolean isConnectionValid = connection.isValid(5); // 5 second timeout
if (isConnectionValid) {
// Execute a simple query to ensure database is responsive
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY);
ResultSet rs = stmt.executeQuery()) {
// Test connection validity
boolean isConnectionValid = connection.isValid(2); // 2 second timeout
if (isConnectionValid) {
// Execute a simple query to ensure database is responsive
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY)) {
stmt.setQueryTimeout(3); // seconds
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next() && rs.getInt(1) == 1) {
long responseTime = System.currentTimeMillis() - startTime;
dbStatus.put("status", "UP");
dbStatus.put("responseTime", responseTime + "ms");
dbStatus.put("message", "Database connection successful");
logger.debug("Database health check: UP ({}ms)", responseTime);
} else {
dbStatus.put("status", "DOWN");
dbStatus.put("message", "Database query returned unexpected result");
logger.warn("Database health check: Query returned unexpected result");
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
97 to 104, shorten the connection.isValid timeout (e.g., from 5 to 1 second) and
set a JDBC statement query timeout on the PreparedStatement before executing
(e.g., stmt.setQueryTimeout(2)); ensure you catch and handle
SQLTimeoutException/SQLException from the query so the health check fails fast
and resources are still closed.

Comment on lines +150 to +159
// Additional test: set and get a test key
String testKey = "health:check:" + System.currentTimeMillis();
String testValue = "test-value";

redisTemplate.opsForValue().set(testKey, testValue);
Object retrievedValue = redisTemplate.opsForValue().get(testKey);
redisTemplate.delete(testKey); // Clean up test key

if (testValue.equals(retrievedValue)) {
redisStatus.put("status", "UP");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix Redis value serialization mismatch; set TTL to avoid lingering keys on failure

When using RedisTemplate<String, Object>, retrievedValue may not be a String (e.g., JDK-serialized), causing equality to fail. Using StringRedisTemplate ensures String-in/String-out. Also, add a short TTL to prevent orphaned keys if delete is skipped due to an exception.

-                // Additional test: set and get a test key
+                // Additional test: set and get a test key
                 String testKey = "health:check:" + System.currentTimeMillis();
                 String testValue = "test-value";
                 
-                redisTemplate.opsForValue().set(testKey, testValue);
-                Object retrievedValue = redisTemplate.opsForValue().get(testKey);
-                redisTemplate.delete(testKey); // Clean up test key
+                stringRedisTemplate.opsForValue().set(testKey, testValue, java.time.Duration.ofSeconds(5));
+                String retrievedValue = stringRedisTemplate.opsForValue().get(testKey);
+                stringRedisTemplate.delete(testKey); // Clean up test key
                 
-                if (testValue.equals(retrievedValue)) {
+                if (testValue.equals(retrievedValue)) {

Note: If you prefer an import over FQCN, add import java.time.Duration; (see import diff suggestion).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
150 to 159, the health-check writes and reads a test key using
RedisTemplate<String, Object>, which can return a non-String (JDK-serialized)
value and break equality; replace or cast to a String-safe API by using
StringRedisTemplate (or ensure the template is configured for String
serialization) so retrievedValue is a String, set the test key with a short TTL
(e.g., Duration.ofSeconds(5)) to avoid orphaned keys if delete fails, and ensure
the delete is performed in a finally block or guarded catch so cleanup is
attempted even on exceptions; add import java.time.Duration if you switch to
using Duration.

Comment on lines +176 to +181
} catch (Exception e) {
long responseTime = System.currentTimeMillis() - startTime;
redisStatus.put("status", "DOWN");
redisStatus.put("message", "Redis connection failed: " + e.getMessage());
redisStatus.put("responseTime", responseTime + "ms");
redisStatus.put("error", e.getClass().getSimpleName());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid leaking exception messages in public health payload

Returning raw exception messages can leak internal hostnames, credentials, or topology. Keep the message generic and log details server-side.

-            redisStatus.put("message", "Redis connection failed: " + e.getMessage());
+            redisStatus.put("message", "Redis connection failed");
             redisStatus.put("responseTime", responseTime + "ms");
             redisStatus.put("error", e.getClass().getSimpleName());
📝 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
} catch (Exception e) {
long responseTime = System.currentTimeMillis() - startTime;
redisStatus.put("status", "DOWN");
redisStatus.put("message", "Redis connection failed: " + e.getMessage());
redisStatus.put("responseTime", responseTime + "ms");
redisStatus.put("error", e.getClass().getSimpleName());
} catch (Exception e) {
long responseTime = System.currentTimeMillis() - startTime;
redisStatus.put("status", "DOWN");
redisStatus.put("message", "Redis connection failed");
redisStatus.put("responseTime", responseTime + "ms");
redisStatus.put("error", e.getClass().getSimpleName());
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
176 to 181, replace the health payload's inclusion of the raw exception message
with a generic message (e.g., "Redis connection failed") and keep error type
minimal (or omit), and move the full exception details into a server-side log
statement (e.g., logger.error with the exception) so the health endpoint does
not expose internal hostnames/credentials; ensure responseTime remains but do
not include e.getMessage() in the returned payload.

@Suraj-kumar00
Copy link
Contributor Author

Hi @drtechie @vishwab1 Is there any update on this?

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.

3 participants