Skip to main content

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:


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 response
  • connection-timeout: 30000 - Max wait time to get connection (30 seconds)
  • idle-timeout: 600000 - Close idle connections after 10 minutes
  • max-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 path
  • show-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 check
  • disk.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:

  1. Check maximum-pool-size setting
  2. Increase pool size if needed
  3. Check for connection leaks (not closing connections)
  4. Monitor connection pool metrics

Actuator Endpoints Not Available

Issue: /actuator/health returns 404.

Solution:

  1. Verify Actuator dependency is in build.gradle
  2. Check management.endpoints.web.exposure.include configuration
  3. Verify base path (/actuator)
  4. Check security configuration (may block actuator endpoints)

Date Serialization Errors

Issue: Date/time fields fail to serialize.

Solution:

  1. Verify jackson-datatype-jsr310 dependency is present
  2. Verify JavaTimeModule is registered in JacksonConfiguration
  3. Check ObjectMapper bean is @Primary
  4. 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-authorized for 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:

  1. Code Quality & Developer Experience - Checkstyle, logging configuration
  2. Code Generation Infrastructure - OpenAPI Generator foundational setup
  3. 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.