Cheat SheetsSpring BootProduction & Advanced

Production & Advanced — Cheat Sheet

Spring Boot · 6 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Production & Advanced
Spring Boot6 topicsQuick revision reference
1

Application Events

Spring's event system decouples components — one service publishes an event without knowing who handles it. @TransactionalEventListener ensures events fire only after the transaction commits, preventing side effects from rolled-back data.

  • Events decouple publishers from listeners — the UserService doesn't know or care about welcome emails.
  • @EventListener methods can be in any Spring bean — listeners discover events by parameter type.
  • @TransactionalEventListener(AFTER_COMMIT) is essential for database-triggered events — prevents firing on rollback.
  • Default event dispatch is synchronous — add @Async to run listeners in a thread pool.
  • @EnableAsync on a @Configuration class is required for @Async to work.
  • Use events for cross-cutting concerns (email, audit, cache invalidation) — not for primary business logic.
Java — ApplicationEventPublisher + @EventListener
// Event object — plain class or Java record
public record UserRegisteredEvent(String userId, String email, Instant registeredAt) {}

// Publisher — inject ApplicationEventPublisher
@Service
public class UserService {
    private final UserRepository userRepository;
    private final ApplicationEventPublisher eventPublisher;

    public UserService(UserRepository userRepository,
                       ApplicationEventPublisher eventPublisher) {
        this.userRepository  = userRepository;
        this.eventPublisher  = eventPublisher;
    }

    @Transactional
    public User register(RegisterRequest req) {
        User user = userRepository.save(new User(req));
        // Publish event — listeners fire after this method returns
        eventPublisher.publishEvent(
            new UserRegisteredEvent(user.getId(), user.getEmail(), Instant.now())
        );
        return user;
    }
}

// Listeners — any @Component can listen
@Component
public class WelcomeEmailListener {
    private final EmailService emailService;

    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        emailService.sendWelcome(event.email());
    }
}

@Component
public class GamificationListener {
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        // Award signup XP
        gamificationService.awardXp(event.userId(), "signup", 10);
    }
}
2

Kafka Messaging

Spring Kafka wraps the Kafka client with KafkaTemplate for producing and @KafkaListener for consuming. Understanding partitions, consumer groups, and error handling is essential for production event-driven architectures.

  • Producer key determines partition assignment — same key always goes to the same partition (ordering guarantee).
  • acks=all + enable.idempotence=true gives at-least-once delivery with duplicate protection on the producer side.
  • Consumer group ID determines the group — each partition is assigned to one consumer per group for parallel processing.
  • concurrency on @KafkaListener sets the number of consumer threads — max useful value equals the partition count.
  • DeadLetterPublishingRecoverer sends failed messages to {topic}.DLT after exhausting retries — prevents consumer blocking.
  • Auto-commit is dangerous — use manual offset commit (AckMode.MANUAL) for exactly-once processing guarantees.
YAML + Java — KafkaTemplate with async send and error handling
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

# application.yml
spring:
  kafka:
    bootstrap-servers: ${KAFKA_BROKERS:localhost:9092}
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      acks: all             # wait for all replicas before acknowledging
      retries: 3
      properties:
        enable.idempotence: true  # exactly-once producer semantics

@Service
public class OrderEventProducer {

    private static final String TOPIC = "order-events";
    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    @Autowired
    public OrderEventProducer(KafkaTemplate<String, OrderEvent> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void publishOrderPlaced(Order order) {
        OrderEvent event = new OrderEvent(order.getId(), "ORDER_PLACED",
                                          order.getCustomerId(), Instant.now());

        // Key = customerId → same customer's orders go to same partition (ordered)
        kafkaTemplate.send(TOPIC, order.getCustomerId(), event)
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    log.error("Failed to publish order event: {}", order.getId(), ex);
                    // Dead-letter, alert, or store for retry
                } else {
                    log.info("Published to partition {} offset {}",
                        result.getRecordMetadata().partition(),
                        result.getRecordMetadata().offset());
                }
            });
    }
}
3

Feign Client

Feign is a declarative HTTP client — define an interface annotated with Spring MVC annotations and Feign generates the implementation. Add Resilience4j for circuit breaking and retry to make inter-service calls production-safe.

  • @EnableFeignClients on your main class triggers scanning for @FeignClient interfaces.
  • Feign interfaces use Spring MVC annotations (@GetMapping, @RequestBody etc.) — familiar and readable.
  • FallbackFactory (preferred over Fallback) receives the exception so you can log and handle it properly.
  • Always configure connect and read timeouts — Feign has no defaults, meaning it can wait forever.
  • Combine with Resilience4j circuit breaker to stop cascading failures when a downstream service is down.
  • For async inter-service calls, use WebClient instead of Feign — Feign is synchronous/blocking.
Java — @FeignClient interface with Spring MVC annotations
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>

// Enable Feign on main class or @Configuration
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApp { ... }

// Feign client interface — Feign generates the implementation
@FeignClient(
    name = "payment-service",
    url = "${services.payment.url:http://payment-service}",
    fallbackFactory = PaymentClientFallbackFactory.class
)
public interface PaymentClient {

    @PostMapping("/api/v1/payments/charge")
    PaymentResponse charge(@RequestBody ChargeRequest request);

    @GetMapping("/api/v1/payments/{paymentId}")
    PaymentResponse getPayment(@PathVariable String paymentId);

    @DeleteMapping("/api/v1/payments/{paymentId}/refund")
    RefundResponse refund(@PathVariable String paymentId);
}

// Inject and use exactly like any other Spring bean
@Service
public class OrderService {
    private final PaymentClient paymentClient;

    public Order placeOrder(PlaceOrderRequest req) {
        // Feign handles HTTP, serialization, error mapping
        PaymentResponse payment = paymentClient.charge(
            new ChargeRequest(req.userId(), req.total(), req.paymentMethod())
        );
        return createOrder(req, payment.getId());
    }
}
4

API Gateway

Spring Cloud Gateway is a reactive API gateway — it routes external traffic to microservices, applies cross-cutting filters (auth, rate limiting, logging), and is configured either in YAML or via Java DSL.

  • Spring Cloud Gateway is reactive (WebFlux) — do not mix it with Spring MVC/Tomcat blocking code.
  • Routes = Predicates (when to match) + Filters (what to transform) + URI (where to forward).
  • Use lb://service-name URIs with Spring Cloud LoadBalancer for client-side load balancing.
  • GlobalFilter applies to all routes; GatewayFilter applies to specific routes.
  • Validate JWTs at the gateway and forward the user ID as a trusted header to downstream services.
  • Circuit breaker + retry filters in the gateway protect against downstream service failures.
YAML — Spring Cloud Gateway route configuration
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

# application.yml — declarative route configuration
spring:
  cloud:
    gateway:
      routes:
        - id: auth-service
          uri: http://auth-service:8080
          predicates:
            - Path=/api/v1/auth/**
          filters:
            - StripPrefix=0   # keep the path as-is

        - id: course-service
          uri: http://course-service:8080
          predicates:
            - Path=/api/v1/courses/**
            - Method=GET,POST
          filters:
            - AddRequestHeader=X-Gateway, spring-cloud-gateway
            - RewritePath=/api/v1/courses/(?<segment>.*), /courses/${segment}
            - CircuitBreaker=name=course-cb,fallbackUri=forward:/fallback

        - id: user-service
          uri: lb://user-service    # lb:// = Spring Cloud LoadBalancer
          predicates:
            - Path=/api/v1/users/**
          filters:
            - name: Retry
              args:
                retries: 3
                methods: GET
                statuses: BAD_GATEWAY
5

Docker & Deployment

A multi-stage Dockerfile builds a lean production image. Docker Compose orchestrates your Spring Boot app with its dependencies (PostgreSQL, Redis) for local development and testing.

  • Multi-stage builds keep production images small — only the JRE and app code, no JDK or Maven.
  • Spring Boot's layered JARs optimize rebuild time — dependency layers rarely change between deploys.
  • Always run the JVM process as a non-root user inside the container.
  • Use -XX:MaxRAMPercentage=75.0 instead of -Xmx in containers — JVM reads the cgroup memory limit.
  • depends_on with service_healthy waits for the database health check before starting the app.
  • Buildpacks (spring-boot:build-image) generate a production image automatically without a Dockerfile.
Dockerfile — multi-stage layered build for Spring Boot
# Stage 1: Build with Maven + JDK
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app

# Download dependencies separately (cacheable layer)
COPY pom.xml .
COPY .mvn/ .mvn
COPY mvnw .
RUN ./mvnw dependency:go-offline -q

# Build
COPY src/ src/
RUN ./mvnw package -DskipTests -q

# Extract layered JAR (Spring Boot 2.3+)
RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted

# Stage 2: Runtime — JRE only, no JDK or Maven
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app

# Non-root user for security
RUN addgroup -S spring && adduser -S spring -G spring
USER spring

# Copy layers from smallest-change (deps) to largest-change (app)
COPY --from=builder /app/target/extracted/dependencies/ ./
COPY --from=builder /app/target/extracted/spring-boot-loader/ ./
COPY --from=builder /app/target/extracted/snapshot-dependencies/ ./
COPY --from=builder /app/target/extracted/application/ ./

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s   CMD wget -q http://localhost:8080/actuator/health -O- | grep -q UP

ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "org.springframework.boot.loader.launch.JarLauncher"]
6

Observability

Observability is knowing what your service is doing in production. The three pillars are metrics (Micrometer → Prometheus), logs (structured with MDC correlation IDs), and traces (Spring Micrometer Tracing → Zipkin/Grafana Tempo).

  • Micrometer is the metrics facade — auto-configures JVM, HTTP, and data source metrics out of the box.
  • @Timed adds latency metrics to methods; Counter tracks occurrence counts; Gauge tracks current values.
  • Tag every metric with application name — required to filter metrics by service in Prometheus/Grafana.
  • MDC injects key-value pairs (correlation ID) into every log line — clear it after each request with MDC.clear().
  • Spring Boot 3 + micrometer-tracing auto-propagates trace/span IDs across service calls and into MDC.
  • Sample 1–10% of traces in production with tracing.sampling.probability — full sampling is expensive.
Java + YAML — Micrometer Counter, Timer, Prometheus export
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    tags:
      application: ${spring.application.name}  # tag all metrics

@Service
public class PaymentService {

    private final MeterRegistry meterRegistry;
    private final Counter paymentCounter;
    private final Timer paymentTimer;

    public PaymentService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;

        this.paymentCounter = Counter.builder("payments.processed")
            .description("Total payments processed")
            .tag("env", "production")
            .register(meterRegistry);

        this.paymentTimer = Timer.builder("payments.duration")
            .description("Payment processing latency")
            .register(meterRegistry);
    }

    public PaymentResult charge(ChargeRequest req) {
        return paymentTimer.recordCallable(() -> {
            PaymentResult result = processCharge(req);
            paymentCounter.increment(1, Tags.of("status", result.status()));
            return result;
        });
    }

    // Simpler: @Timed annotation (requires @EnableAspectJAutoProxy)
    @Timed(value = "payments.duration", description = "Payment charge latency")
    public PaymentResult chargeWithTimed(ChargeRequest req) {
        return processCharge(req);
    }
}
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/spring-boot