Spring BootAdvanced

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 @Async uses SimpleAsyncTaskExecutor (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.

Ready to test yourself?

Sign in to take the quiz, track progress, and earn a certificate.

Sign in