What's new in Spring Boot 3 and Spring Framework 6 (Jakarta EE 10, Java 17 baseline, observability, AOT, Problem Details, virtual threads, RestClient), native images and how GraalVM works (closed-world AOT, reachability metadata), refreshing stale caches with minimal disruption, choosing transaction propagation across "service calls" (and why propagation stops at process boundaries), containerising Spring Boot properly, custom Actuator health for go-live, zero-downtime deployments, and Docker build commands and registries.
Published September 25, 2026
These are "you're responsible for production" questions. Show:
Short answer:
javax.* → jakarta.*), on Spring Framework 6.spring.threads.virtual.enabled, 3.2).RestClient (3.2), plus JdbcClient.@ServiceConnection, 3.1).AutoConfiguration.imports), and @MockitoBean in tests (3.4).spring.factories auto-configuration registration, and many deprecated properties.Migration: use the OpenRewrite Spring Boot 3 recipes, and upgrade to Boot 2.7 first, clearing the deprecations.
Short answer:
ProblemDetail, ErrorResponse).@HttpExchange).RestClient/JdbcClient (6.1).HandlerMethodValidationException, 6.1).SecurityFilterChain only).Spring Framework 7 (late 2025) continues with JSpecify nullness, API versioning, and Jakarta EE 11.
Short answer:
reflect-config.json, RuntimeHints);./mvnw -Pnative native:compile, or spring-boot:build-image with the native profile (buildpacks);@RegisterReflectionForBinding or RuntimeHintsRegistrar.Short answer: First, decide the freshness requirement per data type ("prices must be at most 5 seconds stale; catalogue text can be 1 hour"). Then combine these strategies:
@TransactionalEventListener), and evict or update the affected keys on all instances. Use Redis pub/sub, or Kafka, for local-cache fan-out.refreshAfterWrite in Caffeine): serve the current value while reloading it asynchronously, so users never wait on a cold miss.sync=true), soft TTLs, and stale-while-revalidate.Learn it in depth → Caching Strategies
Short answer: First, clarify what "service calls" means:
REQUIRED (the default) for steps that must commit or roll back together (create order + reserve stock + write the outbox).REQUIRES_NEW for work that must commit independently: an audit or failure log that must persist even if the main transaction rolls back, or a sequence or ID allocation.NESTED (savepoints, with JDBC or DataSourceTransactionManager) for partial rollback of an optional sub-step.MANDATORY for methods that must never run outside a transaction (it's an assertion).NOT_SUPPORTED/NEVER for long reads or remote calls that shouldn't hold a transaction.The pitfalls:
REQUIRES_NEW holds two connections (the outer one is suspended, not released), so under load you can get connection-pool exhaustion or deadlock.UnexpectedRollbackException: an inner REQUIRED method marks the shared transaction rollback-only, even if the caller catches the exception.NESTED isn't supported by the JPA transaction manager (it's supported by DataSourceTransactionManager).Short answer:
Build reproducibly:
./mvnw spring-boot:build-image), Jib, or a multi-stage Dockerfile;java -Djarmode=tools -jar app.jar extract --layers), so dependencies are cached in their own layer;Alpine uses musl, so use JDK builds made for musl, or prefer Debian-slim or distroless glibc images, to avoid surprises.
Run securely: a non-root user, a read-only root file system (with /tmp as a volume), no build tools in the runtime image, image scanning (Trivy or Grype), and signed images (cosign).
Make it container-friendly:
-XX:MaxRAMPercentage=70–75;server.shutdown=graceful, plus a pre-stop delay;/actuator/health/liveness|readiness);Speed up startup: CDS or AOT caches (Boot 3.3+), or native images, where appropriate.
CI/CD: tag images with the Git SHA (immutable), push them to a private registry, promote the same image through the environments, and generate an SBOM.
Learn it in depth → Multi-Stage Builds
Short answer:
HealthIndicators for the critical dependencies: the payment gateway, the Kafka producer, and a third-party API with a cached status. Return UP/DOWN/OUT_OF_SERVICE, with details.management.endpoint.health.group.readiness.include=db,redis,paymentGateway;management.endpoint.health.show-details=when-authorized, not always on a public endpoint (that leaks internals). Expose Actuator on a separate management port, and secure it.orders.placed, payments.failed{provider}, checkout.duration), gauges (queue depth), and @Observed for key flows. Enable histogram percentiles for SLOs./info: build and Git information, for release tracking./actuator/prometheus endpoint, Grafana dashboards, and SLO-based alerts. Wire in readiness gating for deployments.@Component("paymentGateway")
class PaymentGatewayHealth implements HealthIndicator {
private final PaymentClient client;
PaymentGatewayHealth(PaymentClient client) { this.client = client; }
@Override public Health health() {
var status = client.lastKnownStatus(); // cached; don't call the provider per probe
return status.ok() ? Health.up().withDetail("latencyMs", status.p95()).build()
: Health.outOfService().withDetail("reason", status.error()).build();
}
}
Learn it in depth → Health Checks
Short answer:
server.shutdown=graceful, plus a lifecycle timeout).preStop sleep lets the load balancer deregister the pod).maxUnavailable: 0, maxSurge: 1+), with readiness gates;Learn it in depth → Deployment Strategies
Short answer:
docker build -t registry.example.com/shop/orders:1.8.3-3f2c9ab . # build, with an immutable tag (version + Git SHA)
docker run --rm -p 8080:8080 -e SPRING_PROFILES_ACTIVE=dev registry.example.com/shop/orders:1.8.3-3f2c9ab
docker login registry.example.com
docker push registry.example.com/shop/orders:1.8.3-3f2c9ab
# Without a Dockerfile:
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=registry.example.com/shop/orders:1.8.3
# Multi-architecture (amd64 + arm64):
docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/shop/orders:1.8.3 --push .
Storage: a private container registry:
It should have vulnerability scanning, retention and cleanup policies, immutable tags, access control (IAM or robot accounts), and replication for disaster recovery or multiple regions. Docker Hub works for public images, but has rate limits.
Q: What's the biggest effort in a Boot 2 → 3 migration?
A: The javax → jakarta package change (in your code and all your dependencies), Spring Security 6's configuration changes, Hibernate 6 query and dialect differences, removed deprecated properties, and third-party libraries that aren't yet Jakarta-compatible. OpenRewrite automates much of it.
Q: How do you make a Spring application native-image friendly?
A: Avoid runtime reflection and classpath scanning outside Spring's AOT support, register runtime hints for reflection and resources, prefer constructor injection and functional bean registration, test with nativeTest, and keep an eye on library support in the reachability metadata repository.
Q: What does spring.threads.virtual.enabled=true change?
A: Tomcat or Jetty request handling, @Async and @Scheduled task executors, and several integrations switch to virtual threads. Blocking I/O no longer ties up platform threads. Watch out for pinning (before Java 24), and for downstream pool limits.
Q: Why shouldn't readiness include every downstream dependency? A: If a shared dependency goes down, every instance becomes "not ready", and the service disappears from load balancers completely. Include only the dependencies without which the instance truly can't serve useful traffic, and degrade gracefully for the rest.