Feature 7: Production Infrastructure
Purpose: This guide provides step-by-step instructions for implementing production-ready infrastructure enhancements.
Dependencies: No dependencies on other Epic 2 features. Can be implemented independently.
Related Documents:
- Boilerplate Enhancements Solution Architecture - System design overview
- Boilerplate Enhancements Epic - Capability definition
Overview
What This Guide Covers
Production Infrastructure provides production-ready infrastructure and monitoring:
- Hikari connection pool configuration
- Spring Boot Actuator (monitoring and health checks)
- JacksonConfiguration JavaTimeModule (date/time JSON serialization)
What's Included:
- application.yml configuration changes
- build.gradle dependency additions
- Java configuration class enhancement
- Production monitoring endpoints
What's NOT Included:
- ❌ Custom health indicators (can be added later)
- ❌ Custom metrics (can be added later)
- ❌ Business-specific monitoring (product-specific)
Prerequisites
- Springular boilerplate setup
- Understanding of Spring Boot configuration
- Basic understanding of connection pooling
- Basic understanding of Spring Boot Actuator
Implementation Steps
Step 1: Configure Hikari Connection Pool
File: server/src/main/resources/application.yml
Purpose: Configure HikariCP connection pool for production stability and performance.
Key Points:
- ✅ Prevents connection exhaustion
- ✅ Optimizes database resource usage
- ✅ Production-ready defaults
- ✅ Configuration-driven (no code changes)
Find spring.datasource section and add:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
Complete datasource configuration:
spring:
datasource:
url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/springular}
username: ${DATABASE_USERNAME:springular}
password: ${DATABASE_PASSWORD:springular}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 20 # Maximum connections in pool
minimum-idle: 5 # Minimum idle connections
connection-timeout: 30000 # 30 seconds to get connection from pool
idle-timeout: 600000 # 10 minutes - close idle connections
max-lifetime: 1800000 # 30 minutes - maximum connection lifetime
Configuration Explanation:
maximum-pool-size: 20- Maximum concurrent connections (adjust based on load)minimum-idle: 5- Maintains minimum idle connections for faster responseconnection-timeout: 30000- Max wait time to get connection (30 seconds)idle-timeout: 600000- Close idle connections after 10 minutesmax-lifetime: 1800000- Maximum connection lifetime (30 minutes, prevents stale connections)
Benefits:
- Prevents connection exhaustion under load
- Better resource management
- Improved performance (connection reuse)
- Production-ready defaults
Step 2: Add Spring Boot Actuator Dependency
File: server/build.gradle
Purpose: Add Spring Boot Actuator for production monitoring and health checks.
Find dependencies block and add:
dependencies {
// ... existing dependencies ...
// Spring Boot Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
Complete dependencies section example:
dependencies {
// Spring Boot starters
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// ... other dependencies ...
}
Why Actuator:
- Production health checks (
/actuator/health) - Application metrics and monitoring
- Integration with monitoring tools (Prometheus, etc.)
- Database and disk health checks
Step 3: Configure Spring Boot Actuator
File: server/src/main/resources/application.yml
Purpose: Configure Actuator endpoints and health checks.
Key Points:
- ✅ Exposes health and info endpoints
- ✅ Database health check enabled
- ✅ Disk space health check enabled
- ✅ Security-aware (details shown when authorized)
Add management section:
management:
endpoints:
web:
exposure:
include: health,info
base-path: /actuator
endpoint:
health:
show-details: when-authorized
show-components: always
health:
db:
enabled: true
disk:
enabled: true
Complete management configuration:
management:
endpoints:
web:
exposure:
include: health,info # Only expose health and info endpoints
base-path: /actuator # Base path for actuator endpoints
endpoint:
health:
show-details: when-authorized # Show details only when authenticated
show-components: always # Always show component health status
health:
db:
enabled: true # Enable database health check
disk:
enabled: true # Enable disk space health check
Configuration Explanation:
include: health,info- Only exposes health and info endpoints (security best practice)base-path: /actuator- Standard actuator base pathshow-details: when-authorized- Full details only when authenticated (security)show-components: always- Always show component status (useful for monitoring)db.enabled: true- Database connectivity health checkdisk.enabled: true- Disk space health check
Available Endpoints:
/actuator/health- Application health status/actuator/info- Application information
Step 4: Add Jackson JSR310 Dependency
File: server/build.gradle
Purpose: Add Jackson JSR310 module for Java 8 date/time type support.
Find dependencies block and add:
dependencies {
// ... existing dependencies ...
// Jackson JSR310 (Java 8 date/time support)
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
}
Why This Dependency:
- Supports Java 8 date/time types (OffsetDateTime, LocalDateTime, ZonedDateTime, etc.)
- Required for proper JSON serialization/deserialization
- Works with JavaTimeModule (next step)
Step 5: Enhance JacksonConfiguration with JavaTimeModule
File: server/src/main/java/com/saas/springular/common/config/JacksonConfiguration.java
Purpose: Register JavaTimeModule for proper Java 8 date/time JSON serialization.
Key Points:
- ✅ Enables proper serialization of Java 8 date/time types
- ✅ Prevents serialization errors
- ✅ Standard practice for modern Spring Boot applications
Find the ObjectMapper bean method and add JavaTimeModule:
package com.saas.springular.common.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class JacksonConfiguration {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
}
Before (missing JavaTimeModule):
@Bean
@Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
After (with JavaTimeModule):
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
Why JavaTimeModule:
- Java 8 date/time types (OffsetDateTime, LocalDateTime, etc.) require special serialization
- Without this module, dates may serialize incorrectly or fail
- Standard practice for Spring Boot applications using modern Java date/time APIs
Verification
Step 1: Verify Connection Pool Configuration
Check application startup logs:
./gradlew bootRun
Expected Result:
- Application starts successfully
- No connection pool errors
- HikariCP logs connection pool initialization
Verify Connection Pool:
- Check logs for HikariCP initialization messages
- Verify pool size settings are applied
- Test database connectivity works
Step 2: Verify Actuator Endpoints
Check health endpoint:
curl http://localhost:8080/actuator/health
Expected Response:
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "PostgreSQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 500000000000,
"free": 400000000000,
"threshold": 10485760,
"exists": true
}
},
"ping": {
"status": "UP"
}
}
}
Check info endpoint:
curl http://localhost:8080/actuator/info
Expected Response:
{}
(Empty by default, can be configured with build info)
Verify Components:
- Database health check returns UP/DOWN
- Disk space health check returns UP/DOWN
- Ping component returns UP
Step 3: Verify JavaTimeModule Registration
Create a simple test endpoint (temporary, for verification):
File: server/src/main/java/com/saas/springular/common/config/JacksonConfigurationTestController.java (temporary)
package com.saas.springular.common.config;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.OffsetDateTime;
@RestController
@RequestMapping("/api/test")
public class JacksonConfigurationTestController {
@GetMapping("/date")
public DateResponse testDate() {
return new DateResponse(OffsetDateTime.now());
}
public record DateResponse(OffsetDateTime timestamp) {}
}
Test the endpoint:
curl http://localhost:8080/api/test/date
Expected Response:
{
"timestamp": "2024-01-01T12:00:00Z"
}
Verify Serialization:
- Date serializes in ISO-8601 format
- No serialization errors
- Date deserializes correctly
Delete test controller after verification.
Testing
Unit Tests
No unit tests required (configuration changes).
Integration Tests
Verify Actuator endpoints work:
- Health endpoint returns valid JSON
- Database health check works
- Disk space health check works
Verify connection pooling:
- Multiple concurrent requests handled correctly
- Connection pool limits respected
- No connection exhaustion errors
Verify date serialization:
- Existing tests continue to pass
- Date/time fields serialize correctly in responses
Troubleshooting
Connection Pool Exhaustion
Issue: Errors like "Connection is not available, request timed out".
Solution:
- Check
maximum-pool-sizesetting - Increase pool size if needed
- Check for connection leaks (not closing connections)
- Monitor connection pool metrics
Actuator Endpoints Not Available
Issue: /actuator/health returns 404.
Solution:
- Verify Actuator dependency is in build.gradle
- Check
management.endpoints.web.exposure.includeconfiguration - Verify base path (
/actuator) - Check security configuration (may block actuator endpoints)
Date Serialization Errors
Issue: Date/time fields fail to serialize.
Solution:
- Verify
jackson-datatype-jsr310dependency is present - Verify
JavaTimeModuleis registered in JacksonConfiguration - Check ObjectMapper bean is @Primary
- Verify date/time types are Java 8 types (not java.util.Date)
Production Considerations
Connection Pool Sizing
Adjust based on:
- Application load
- Database capacity
- Number of instances
- Connection requirements
Monitoring:
- Monitor connection pool metrics
- Track connection wait times
- Monitor connection pool utilization
Actuator Security
Best Practices:
- Only expose necessary endpoints
- Use
show-details: when-authorizedfor production - Consider securing actuator endpoints with Spring Security
- Don't expose sensitive endpoints (env, configprops, etc.)
Health Check Integration
Integration Options:
- Kubernetes liveness/readiness probes
- Load balancer health checks
- Monitoring systems (Prometheus, etc.)
- Alerting systems
Next Steps
After completing this guide:
- ✅ Code Quality & Developer Experience - Checkstyle, logging configuration
- ✅ Code Generation Infrastructure - OpenAPI Generator foundational setup
- ✅ Test Infrastructure - Test profile configuration
Summary
This implementation provides:
- ✅ Connection Pooling: Production-ready HikariCP configuration
- ✅ Monitoring: Actuator health checks and metrics
- ✅ Date Serialization: Proper Java 8 date/time support
- ✅ Production Readiness: Infrastructure for production deployment
All changes are high priority for production and improve application stability and observability.