Spring Boot Production: Profiles, Actuator, Testing & Resilience
Prepare Spring Boot services for real environments: profiles and externalized config, Actuator health checks, integration testing with @SpringBootTest and MockMvc, caching, async processing, and production hardening.
4 sections · ~35 min · 5-question quiz (pass ≥ 70%)
1Profiles and Externalized Configuration
Hard-coding environment settings is a deployment trap. Spring Boot externalizes configuration through a well-defined precedence chain: command-line args beat env vars, which beat application-{profile}.properties, which beat application.properties.
# application.properties — shared defaults
spring.application.name=empforge
# application-dev.properties
spring.datasource.url=jdbc:h2:mem:devdb
logging.level.root=DEBUG
# application-prod.properties
spring.datasource.url=${DATABASE_URL}
logging.level.root=WARN
Activate a profile:
java -jar app.jar --spring.profiles.active=prod
# or: SPRING_PROFILES_ACTIVE=prod
Use @Profile("prod") on beans that should only exist in certain environments (e.g., a real email sender vs. a dev no-op).
Rules of thumb:
- Secrets never belong in source control — inject via environment variables or a secret manager.
- Keep prod defaults safe: fail closed, minimal logging noise, connection pools sized for expected load.
- Document every custom property; surprise env vars cause 3 a.m. pages.
2Spring Boot Actuator: Observability Endpoints
Actuator exposes operational endpoints for health checks, metrics, and diagnostics. Add the starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Key endpoints (enabled selectively in production):
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=when_authorized
GET /actuator/health
# { "status": "UP", "components": { "db": { "status": "UP" } } }
Implement custom health indicators for dependencies your app relies on:
@Component
public class PaymentGatewayHealth implements HealthIndicator {
public Health health() {
return gateway.isReachable()
? Health.up().build()
: Health.down().withDetail("reason", "timeout").build();
}
}
Wire Actuator health to your load balancer or Kubernetes liveness/readiness probes. Expose metrics to Prometheus via micrometer-registry-prometheus for dashboards and alerting.
3Testing: @SpringBootTest and MockMvc
Spring Boot's test starters give you layered testing tools:
Slice tests (@WebMvcTest, @DataJpaTest) load only part of the context — fast and focused.
Full integration tests use @SpringBootTest:
@SpringBootTest
@AutoConfigureMockMvc
class EmployeeControllerIT {
@Autowired MockMvc mockMvc;
@Test
void listReturnsEmployees() throws Exception {
mockMvc.perform(get("/api/employees"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").exists());
}
}
MockMvc simulates HTTP without starting a real network port — perfect for controller tests. Use @MockBean to replace collaborators with mocks inside the Spring context.
For end-to-end tests against a real server:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class FullStackIT {
@Autowired TestRestTemplate rest;
}
Rules of thumb:
- Prefer slice tests for unit-speed feedback; reserve full context tests for critical paths.
- Use Testcontainers for integration tests that need a real Postgres or Redis — in-memory substitutes lie.
4Caching, @Async, and Production Concerns
Caching avoids repeated expensive work. Enable it with one annotation:
@Configuration
@EnableCaching
public class CacheConfig {}
@Service
public class ReportService {
@Cacheable("monthlyReports")
public Report generateMonthlyReport(String dept) {
return expensiveAggregation(dept);
}
@CacheEvict(value = "monthlyReports", allEntries = true)
public void invalidateAllReports() { }
}
Back caches with Caffeine (in-memory) or Redis (distributed) depending on scale.
Async processing offloads work from request threads:
@Configuration
@EnableAsync
public class AsyncConfig {}
@Service
public class NotificationService {
@Async
public CompletableFuture<Void> sendWelcomeEmail(String email) {
mailClient.send(email);
return CompletableFuture.completedFuture(null);
}
}
Production checklist:
- Size thread pools explicitly — default
@AsyncusesSimpleAsyncTaskExecutor(new thread per task!), which does not scale. - Add timeouts and circuit breakers (Resilience4j) on external calls.
- Configure graceful shutdown (
server.shutdown=graceful) so in-flight requests finish during deploys. - Monitor GC, heap, and thread pools — performance surprises show up under load, not on your laptop.