Advanced

This tutorial provides 25 advanced Spring Framework examples (51-75) covering production-ready patterns. Focus includes REST APIs, Security, caching, async execution, and comprehensive testing strategies.

Coverage: 75-95% of Spring Framework features Prerequisites: Complete Beginner and Intermediate tutorials first

REST API Development (Examples 51-55)

Example 51: @RestController and ResponseEntity (Coverage: 76.5%)

Demonstrates RESTful API with proper HTTP responses.

Java Implementation:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController  // => @Controller + @ResponseBody combined
@RequestMapping("/api/donations")
public class DonationRestController {
    @GetMapping("/{id}")
    public ResponseEntity<String> getById(@PathVariable Long id) {
        // => ResponseEntity controls HTTP status and headers

        if (id <= 0) {
            return ResponseEntity.badRequest().body("Invalid ID");
            // => Returns HTTP 400 Bad Request
        }

        String donation = "Donation #" + id;
        return ResponseEntity.ok(donation);
        // => Returns HTTP 200 OK with body
    }

    @PostMapping
    public ResponseEntity<String> create(@RequestBody String donor) {
        String result = "Created for " + donor;

        return ResponseEntity
            .status(HttpStatus.CREATED)  // => HTTP 201 Created
            .header("Location", "/api/donations/123")  // => Custom header
            .body(result);
        // => Full control over response
    }
}

Kotlin Implementation:

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController  // => @Controller + @ResponseBody combined
@RequestMapping("/api/donations")
class DonationRestController {
    @GetMapping("/{id}")
    fun getById(@PathVariable id: Long): ResponseEntity<String> {
        // => ResponseEntity controls HTTP status and headers

        return if (id <= 0) {
            ResponseEntity.badRequest().body("Invalid ID")
            // => Returns HTTP 400 Bad Request
        } else {
            val donation = "Donation #$id"
            ResponseEntity.ok(donation)
            // => Returns HTTP 200 OK with body
        }
    }

    @PostMapping
    fun create(@RequestBody donor: String): ResponseEntity<String> {
        val result = "Created for $donor"

        return ResponseEntity
            .status(HttpStatus.CREATED)  // => HTTP 201 Created
            .header("Location", "/api/donations/123")  // => Custom header
            .body(result)
        // => Full control over response
    }
}

Key Takeaways:

  • ResponseEntity provides full HTTP response control
  • Fluent builder API for status, headers, body
  • Type-safe response bodies
  • RESTful status codes (200, 201, 400, etc.)

Related Documentation:


Example 52: Content Negotiation (Coverage: 78.0%)

Demonstrates producing different content types.

Java Implementation:

import org.springframework.web.bind.annotation.*;

class Donation {
    private String donor;
    private double amount;

    public Donation(String donor, double amount) {
        this.donor = donor;
        this.amount = amount;
    }

    public String getDonor() { return donor; }
    public double getAmount() { return amount; }
}

@RestController
@RequestMapping("/api/donations")
public class ContentNegotiationController {
    @GetMapping(produces = "application/json")
    // => Produces JSON when Accept: application/json
    public Donation getAsJson() {
        return new Donation("Ali", 500.0);
        // => Serialized to: {"donor":"Ali","amount":500.0}
    }

    @GetMapping(produces = "application/xml")
    // => Produces XML when Accept: application/xml
    public Donation getAsXml() {
        return new Donation("Fatima", 300.0);
        // => Serialized to: <Donation><donor>Fatima</donor>...</Donation>
        // => Requires JAXB annotations on Donation class
    }
}

Kotlin Implementation:

import org.springframework.web.bind.annotation.*

data class Donation(val donor: String, val amount: Double)

@RestController
@RequestMapping("/api/donations")
class ContentNegotiationController {
    @GetMapping(produces = ["application/json"])
    // => Produces JSON when Accept: application/json
    fun getAsJson(): Donation {
        return Donation("Ali", 500.0)
        // => Serialized to: {"donor":"Ali","amount":500.0}
    }

    @GetMapping(produces = ["application/xml"])
    // => Produces XML when Accept: application/xml
    fun getAsXml(): Donation {
        return Donation("Fatima", 300.0)
        // => Serialized to: <Donation><donor>Fatima</donor>...</Donation>
        // => Requires JAXB annotations on Donation class
    }
}

Key Takeaways:

  • produces attribute specifies content types
  • Content negotiation via Accept header
  • Jackson handles JSON automatically
  • JAXB for XML serialization

Related Documentation:


Example 53: CORS Configuration (Coverage: 79.5%)

Demonstrates Cross-Origin Resource Sharing setup.

Java Implementation:

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
@CrossOrigin(
    origins = "http://localhost:3000",  // => Allowed origin
    methods = {RequestMethod.GET, RequestMethod.POST},  // => Allowed methods
    allowedHeaders = "*",  // => All headers allowed
    maxAge = 3600  // => Preflight cache duration (seconds)
)
public class CorsController {
    @GetMapping("/data")
    public String getData() {
        return "CORS enabled data";
        // => Accessible from http://localhost:3000
    }
}

Kotlin Implementation:

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api")
@CrossOrigin(
    origins = ["http://localhost:3000"],  // => Allowed origin
    methods = [RequestMethod.GET, RequestMethod.POST],  // => Allowed methods
    allowedHeaders = ["*"],  // => All headers allowed
    maxAge = 3600  // => Preflight cache duration (seconds)
)
class CorsController {
    @GetMapping("/data")
    fun getData(): String {
        return "CORS enabled data"
        // => Accessible from http://localhost:3000
    }
}

Key Takeaways:

  • @CrossOrigin enables CORS
  • Specify allowed origins, methods, headers
  • Method-level or class-level annotation
  • Global CORS config also possible

Related Documentation:


Example 54: API Versioning (Coverage: 81.0%)

Demonstrates REST API versioning strategies.

Java Implementation:

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/donations")
// => URI versioning: version in path
public class DonationsV1Controller {
    @GetMapping
    public String listV1() {
        return "Donations API v1";
        // => Simple string response
    }
}

@RestController
@RequestMapping("/api/v2/donations")
// => Version 2 with improved response
public class DonationsV2Controller {
    @GetMapping
    public DonationResponse listV2() {
        return new DonationResponse("Enhanced v2 response");
        // => Structured response object
    }
}

class DonationResponse {
    private String message;
    public DonationResponse(String message) { this.message = message; }
    public String getMessage() { return message; }
}

Kotlin Implementation:

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/v1/donations")
// => URI versioning: version in path
class DonationsV1Controller {
    @GetMapping
    fun listV1(): String {
        return "Donations API v1"
        // => Simple string response
    }
}

@RestController
@RequestMapping("/api/v2/donations")
// => Version 2 with improved response
class DonationsV2Controller {
    @GetMapping
    fun listV2(): DonationResponse {
        return DonationResponse("Enhanced v2 response")
        // => Structured response object
    }
}

data class DonationResponse(val message: String)

Key Takeaways:

  • URI versioning most common (/v1/, /v2/)
  • Separate controllers per version
  • Maintains backward compatibility
  • Can deprecate old versions gradually

Related Documentation:


Example 55: Global Exception Handler (Coverage: 82.5%)

Demonstrates centralized exception handling.

Java Implementation:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

class ErrorResponse {
    private String message;
    private int status;

    public ErrorResponse(String message, int status) {
        this.message = message;
        this.status = status;
    }

    public String getMessage() { return message; }
    public int getStatus() { return status; }
}

@RestControllerAdvice  // => Global exception handler for all controllers
public class GlobalExceptionHandler {
    @ExceptionHandler(IllegalArgumentException.class)
    // => Handles IllegalArgumentException from any controller
    public ResponseEntity<ErrorResponse> handleIllegalArgument(
        IllegalArgumentException ex
    ) {
        ErrorResponse error = new ErrorResponse(ex.getMessage(), 400);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
        // => Returns 400 with error details
    }

    @ExceptionHandler(Exception.class)
    // => Catch-all for unhandled exceptions
    public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
        ErrorResponse error = new ErrorResponse("Internal error", 500);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
        // => Returns 500 for unexpected errors
    }
}

Kotlin Implementation:

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

data class ErrorResponse(val message: String, val status: Int)

@RestControllerAdvice  // => Global exception handler for all controllers
class GlobalExceptionHandler {
    @ExceptionHandler(IllegalArgumentException::class)
    // => Handles IllegalArgumentException from any controller
    fun handleIllegalArgument(ex: IllegalArgumentException): ResponseEntity<ErrorResponse> {
        val error = ErrorResponse(ex.message ?: "Bad request", 400)
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error)
        // => Returns 400 with error details
    }

    @ExceptionHandler(Exception::class)
    // => Catch-all for unhandled exceptions
    fun handleGeneral(ex: Exception): ResponseEntity<ErrorResponse> {
        val error = ErrorResponse("Internal error", 500)
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error)
        // => Returns 500 for unexpected errors
    }
}

Key Takeaways:

  • @RestControllerAdvice for global handlers
  • Consistent error responses across all endpoints
  • Multiple @ExceptionHandler methods
  • Prioritizes specific exceptions over general

Related Documentation:


Spring Security (Examples 56-60)

Example 56: Basic Security Configuration (Coverage: 84.0%)

Demonstrates securing endpoints with Spring Security.

Java Implementation:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                // => Public endpoints (no auth required)

                .requestMatchers("/api/**").authenticated()
                // => API endpoints require authentication

                .anyRequest().denyAll()
                // => Deny all other requests
            )
            .httpBasic();  // => HTTP Basic authentication

        return http.build();  // => Builds security filter chain
    }
}

Kotlin Implementation:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
class SecurityConfig {
    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http
            .authorizeHttpRequests { auth ->
                auth
                    .requestMatchers("/public/**").permitAll()
                    // => Public endpoints (no auth required)

                    .requestMatchers("/api/**").authenticated()
                    // => API endpoints require authentication

                    .anyRequest().denyAll()
                    // => Deny all other requests
            }
            .httpBasic { }  // => HTTP Basic authentication

        return http.build()  // => Builds security filter chain
    }
}

Spring Security Filter Chain:

  sequenceDiagram
    participant Request
    participant Filter1 as SecurityContextFilter
    participant Filter2 as AuthenticationFilter
    participant Filter3 as AuthorizationFilter
    participant Controller

    Request->>Filter1: Incoming HTTP request
    Note over Filter1: Initialize security context

    Filter1->>Filter2: Pass to authentication
    Note over Filter2: Verify credentials<br/>(Basic Auth, JWT, etc.)

    Filter2->>Filter3: Authenticated request
    Note over Filter3: Check URL authorization<br/>/public/** → permitAll<br/>/api/** → authenticated

    Filter3->>Controller: Authorized request
    Note over Controller: Execute business logic

    Controller-->>Request: HTTP response

    style Request fill:#0173B2,stroke:#000,color:#fff
    style Filter1 fill:#DE8F05,stroke:#000,color:#000
    style Filter2 fill:#029E73,stroke:#000,color:#fff
    style Filter3 fill:#CC78BC,stroke:#000,color:#000
    style Controller fill:#CA9161,stroke:#000,color:#fff

Diagram Explanation: This sequence diagram shows how Spring Security’s filter chain processes requests through multiple security filters (context, authentication, authorization) before reaching the controller.

Key Takeaways:

  • SecurityFilterChain configures security rules
  • authorizeHttpRequests() defines URL patterns
  • permitAll(), authenticated(), denyAll() control access
  • httpBasic() enables basic authentication

Related Documentation:


Example 57: Method Security (Coverage: 85.5%)

Demonstrates securing methods with annotations.

Java Implementation:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class SecureService {
    @PreAuthorize("hasRole('ADMIN')")
    // => Only users with ADMIN role can access
    // => Throws AccessDeniedException if unauthorized
    public void adminOperation() {
        System.out.println("Admin operation executed");
    }

    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    // => Users with USER OR ADMIN role allowed
    public void userOperation() {
        System.out.println("User operation executed");
    }

    @PreAuthorize("#username == authentication.name")
    // => SpEL expression: user can only access own data
    // => #username param must match authenticated user
    public void accessOwnData(String username) {
        System.out.println("Accessing data for: " + username);
    }
}

Kotlin Implementation:

import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Service

@Service
class SecureService {
    @PreAuthorize("hasRole('ADMIN')")
    // => Only users with ADMIN role can access
    // => Throws AccessDeniedException if unauthorized
    fun adminOperation() {
        println("Admin operation executed")
    }

    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    // => Users with USER OR ADMIN role allowed
    fun userOperation() {
        println("User operation executed")
    }

    @PreAuthorize("#username == authentication.name")
    // => SpEL expression: user can only access own data
    // => #username param must match authenticated user
    fun accessOwnData(username: String) {
        println("Accessing data for: $username")
    }
}

Key Takeaways:

  • @PreAuthorize before method execution
  • SpEL expressions for complex rules
  • hasRole(), hasAnyRole() for role checks
  • Access method parameters in expressions

Related Documentation:


Example 58: Custom UserDetailsService (Coverage: 87.0%)

Demonstrates custom user authentication.

Java Implementation:

import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException {
        // => Called during authentication
        // => Load user from database

        if ("admin".equals(username)) {
            return User.builder()
                .username("admin")
                .password("{noop}password")  // => {noop} = no password encoding
                .authorities(List.of(
                    new SimpleGrantedAuthority("ROLE_ADMIN")
                    // => Grants ADMIN role
                ))
                .build();
            // => Returns UserDetails for Spring Security
        }

        throw new UsernameNotFoundException("User not found: " + username);
        // => Authentication fails
    }
}

Kotlin Implementation:

import org.springframework.security.core.userdetails.*
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService : UserDetailsService {
    override fun loadUserByUsername(username: String): UserDetails {
        // => Called during authentication
        // => Load user from database

        return if (username == "admin") {
            User.builder()
                .username("admin")
                .password("{noop}password")  // => {noop} = no password encoding
                .authorities(listOf(
                    SimpleGrantedAuthority("ROLE_ADMIN")
                    // => Grants ADMIN role
                ))
                .build()
            // => Returns UserDetails for Spring Security
        } else {
            throw UsernameNotFoundException("User not found: $username")
            // => Authentication fails
        }
    }
}

Custom UserDetailsService Authentication Flow:

  sequenceDiagram
    participant Client
    participant Security as Spring Security
    participant UserService as CustomUserDetailsService
    participant Database

    Client->>Security: Login with username/password
    Security->>UserService: loadUserByUsername(username)

    UserService->>Database: Query user by username
    Database-->>UserService: User data (or not found)

    alt User Found
        UserService-->>Security: UserDetails object<br/>(username, password, roles)
        Security->>Security: Validate password
        Security-->>Client: Authentication success
    else User Not Found
        UserService-->>Security: UsernameNotFoundException
        Security-->>Client: Authentication failure (401)
    end

    style Client fill:#0173B2,stroke:#000,color:#fff
    style Security fill:#DE8F05,stroke:#000,color:#000
    style UserService fill:#029E73,stroke:#000,color:#fff
    style Database fill:#CC78BC,stroke:#000,color:#000

Diagram Explanation: This sequence diagram shows how Spring Security delegates authentication to CustomUserDetailsService, which loads user data and returns UserDetails for password verification.

Key Takeaways:

  • UserDetailsService loads user data
  • Called automatically during authentication
  • Return UserDetails with username, password, authorities
  • UsernameNotFoundException for unknown users

Related Documentation:


Example 59: JWT Token Authentication (Coverage: 88.5%)

Demonstrates JWT-based authentication (simplified).

Java Implementation:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;
import java.util.Date;

@Service
public class JwtService {
    private static final String SECRET = "mySecretKey";
    // => Secret key for signing tokens
    // => In production: use strong secret, store securely

    public String generateToken(String username) {
        return Jwts.builder()
            .setSubject(username)  // => Token subject (username)
            .setIssuedAt(new Date())  // => Issue time
            .setExpiration(new Date(System.currentTimeMillis() + 86400000))
            // => Expiration: 24 hours from now
            .signWith(SignatureAlgorithm.HS256, SECRET)
            // => Sign with HMAC SHA-256
            .compact();
        // => Returns JWT string
    }

    public String extractUsername(String token) {
        return Jwts.parser()
            .setSigningKey(SECRET)  // => Verify signature
            .parseClaimsJws(token)  // => Parse and validate
            .getBody()
            .getSubject();  // => Extract username from subject claim
    }
}

Kotlin Implementation:

import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.springframework.stereotype.Service
import java.util.Date

@Service
class JwtService {
    companion object {
        private const val SECRET = "mySecretKey"
        // => Secret key for signing tokens
        // => In production: use strong secret, store securely
    }

    fun generateToken(username: String): String {
        return Jwts.builder()
            .setSubject(username)  // => Token subject (username)
            .setIssuedAt(Date())  // => Issue time
            .setExpiration(Date(System.currentTimeMillis() + 86400000))
            // => Expiration: 24 hours from now
            .signWith(SignatureAlgorithm.HS256, SECRET)
            // => Sign with HMAC SHA-256
            .compact()
        // => Returns JWT string
    }

    fun extractUsername(token: String): String {
        return Jwts.parser()
            .setSigningKey(SECRET)  // => Verify signature
            .parseClaimsJws(token)  // => Parse and validate
            .body
            .subject  // => Extract username from subject claim
    }
}

Key Takeaways:

  • JWT for stateless authentication
  • Tokens contain claims (subject, expiration)
  • Signed with secret key
  • Requires JJWT library dependency

Related Documentation:


Example 60: Password Encoding (Coverage: 90.0%)

Demonstrates secure password storage.

Java Implementation:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Configuration
public class PasswordConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
        // => BCrypt hashing algorithm
        // => Slow by design (protects against brute force)
    }
}

@Service
class PasswordService {
    private final PasswordEncoder encoder;

    public PasswordService(PasswordEncoder encoder) {
        this.encoder = encoder;
    }

    public String encodePassword(String raw) {
        String encoded = encoder.encode(raw);
        // => Hashes password with random salt
        // => Same input produces different hash each time

        System.out.println("Raw: " + raw);
        System.out.println("Encoded: " + encoded);
        return encoded;
    }

    public boolean matches(String raw, String encoded) {
        return encoder.matches(raw, encoded);
        // => Verifies password against hash
        // => Returns true if match
    }
}

Kotlin Implementation:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service

@Configuration
class PasswordConfig {
    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
        // => BCrypt hashing algorithm
        // => Slow by design (protects against brute force)
    }
}

@Service
class PasswordService(private val encoder: PasswordEncoder) {
    fun encodePassword(raw: String): String {
        val encoded = encoder.encode(raw)
        // => Hashes password with random salt
        // => Same input produces different hash each time

        println("Raw: $raw")
        println("Encoded: $encoded")
        return encoded
    }

    fun matches(raw: String, encoded: String): Boolean {
        return encoder.matches(raw, encoded)
        // => Verifies password against hash
        // => Returns true if match
    }
}

Key Takeaways:

  • Never store plain passwords
  • BCrypt recommended for password hashing
  • Random salt prevents rainbow table attacks
  • matches() for verification

Related Documentation:


Advanced Patterns (Examples 61-65)

Example 61: Spring Cache Abstraction (Coverage: 91.5%)

Demonstrates method-level caching.

Java Implementation:

import org.springframework.cache.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

@Configuration
@EnableCaching  // => Enables Spring caching support
public class CacheConfig {
}

@Service
@CacheConfig(cacheNames = "donations")
// => Default cache name for this class
class DonationCacheService {
    @Cacheable  // => Result cached after first call
                // => Subsequent calls return cached value
    public String getDonation(Long id) {
        System.out.println("Loading from database: " + id);
        // => Only printed on cache miss
        return "Donation #" + id;
    }

    @CachePut(key = "#id")
    // => Updates cache with new value
    // => Method always executed
    public String updateDonation(Long id, String data) {
        System.out.println("Updating: " + id);
        return data;  // => Cached
    }

    @CacheEvict(key = "#id")
    // => Removes from cache
    // => Next getDonation() will reload
    public void deleteDonation(Long id) {
        System.out.println("Deleting: " + id);
    }
}

Kotlin Implementation:

import org.springframework.cache.annotation.*
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Service

@Configuration
@EnableCaching  // => Enables Spring caching support
class CacheConfig

@Service
@CacheConfig(cacheNames = ["donations"])
// => Default cache name for this class
class DonationCacheService {
    @Cacheable  // => Result cached after first call
                // => Subsequent calls return cached value
    fun getDonation(id: Long): String {
        println("Loading from database: $id")
        // => Only printed on cache miss
        return "Donation #$id"
    }

    @CachePut(key = "#id")
    // => Updates cache with new value
    // => Method always executed
    fun updateDonation(id: Long, data: String): String {
        println("Updating: $id")
        return data  // => Cached
    }

    @CacheEvict(key = "#id")
    // => Removes from cache
    // => Next getDonation() will reload
    fun deleteDonation(id: Long) {
        println("Deleting: $id")
    }
}

Cache Abstraction Lifecycle:

  stateDiagram-v2
    [*] --> CacheMiss: getDonation(1) called
    CacheMiss --> LoadFromDB: Key not in cache
    LoadFromDB --> CacheStore: Store result
    CacheStore --> CacheHit: Subsequent getDonation(1)

    CacheHit --> CacheUpdate: updateDonation(1, data)
    CacheUpdate --> CacheHit: @CachePut updates cache

    CacheHit --> CacheEvict: deleteDonation(1)
    CacheEvict --> [*]: @CacheEvict removes entry

    note right of CacheMiss
        @Cacheable checks cache
        If miss, executes method
    end note

    note right of CacheUpdate
        @CachePut always executes
        Updates cached value
    end note

    note right of CacheEvict
        @CacheEvict removes entry
        Next access will be miss
    end note

Diagram Explanation: This state diagram shows the cache lifecycle - from initial miss/load, through hits, updates via @CachePut, and eviction via @CacheEvict.

Key Takeaways:

  • @Cacheable caches method results
  • @CachePut updates cache
  • @CacheEvict removes from cache
  • @EnableCaching required

Related Documentation:


Example 62: Async Method Execution (Coverage: 93.0%)

Demonstrates asynchronous processing with @Async.

Java Implementation:

import org.springframework.scheduling.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Configuration
@EnableAsync  // => Enables @Async support
public class AsyncConfig {
}

@Service
class NotificationService {
    @Async  // => Method runs in separate thread
            // => Caller doesn't wait for completion
    public void sendEmail(String to, String message) {
        System.out.println("Sending email to: " + to);
        // => Runs asynchronously
        try {
            Thread.sleep(2000);  // => Simulates slow operation
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Email sent to: " + to);
    }

    @Async
    public CompletableFuture<String> processAsync(String data) {
        // => Returns CompletableFuture for async result
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return CompletableFuture.completedFuture("Processed: " + data);
        // => Caller can wait for result if needed
    }
}

Kotlin Implementation:

import org.springframework.scheduling.annotation.*
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Service
import java.util.concurrent.CompletableFuture

@Configuration
@EnableAsync  // => Enables @Async support
class AsyncConfig

@Service
class NotificationService {
    @Async  // => Method runs in separate thread
            // => Caller doesn't wait for completion
    fun sendEmail(to: String, message: String) {
        println("Sending email to: $to")
        // => Runs asynchronously
        Thread.sleep(2000)  // => Simulates slow operation
        println("Email sent to: $to")
    }

    @Async
    fun processAsync(data: String): CompletableFuture<String> {
        // => Returns CompletableFuture for async result
        Thread.sleep(1000)

        return CompletableFuture.completedFuture("Processed: $data")
        // => Caller can wait for result if needed
    }
}

Key Takeaways:

  • @Async executes method in separate thread
  • void methods fire-and-forget
  • CompletableFuture for async results
  • @EnableAsync required

Related Documentation:


Example 63: Application Events (Coverage: 94.5%)

Demonstrates event-driven architecture.

Java Implementation:

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

class DonationEvent extends ApplicationEvent {
    // => Custom event class
    private final String donor;
    private final double amount;

    public DonationEvent(Object source, String donor, double amount) {
        super(source);  // => Event source
        this.donor = donor;
        this.amount = amount;
    }

    public String getDonor() { return donor; }
    public double getAmount() { return amount; }
}

@Service
class DonationService {
    private final ApplicationEventPublisher publisher;

    public DonationService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;  // => Injected event publisher
    }

    public void createDonation(String donor, double amount) {
        System.out.println("Creating donation");

        publisher.publishEvent(new DonationEvent(this, donor, amount));
        // => Publishes event to all listeners
        // => Synchronous by default
    }
}

@Component
class DonationEventListener {
    @EventListener  // => Listens for DonationEvent
    public void handleDonation(DonationEvent event) {
        // => Called when event published
        System.out.println("Event received: " + event.getDonor() +
                           " donated $" + event.getAmount());
    }
}

Kotlin Implementation:

import org.springframework.context.ApplicationEvent
import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component
import org.springframework.stereotype.Service

class DonationEvent(
    source: Any,
    val donor: String,
    val amount: Double
) : ApplicationEvent(source)  // => Custom event class

@Service
class DonationService(
    private val publisher: ApplicationEventPublisher
    // => Injected event publisher
) {
    fun createDonation(donor: String, amount: Double) {
        println("Creating donation")

        publisher.publishEvent(DonationEvent(this, donor, amount))
        // => Publishes event to all listeners
        // => Synchronous by default
    }
}

@Component
class DonationEventListener {
    @EventListener  // => Listens for DonationEvent
    fun handleDonation(event: DonationEvent) {
        // => Called when event published
        println("Event received: ${event.donor} donated $${event.amount}")
    }
}

ApplicationEvent Publishing Flow:

  sequenceDiagram
    participant Service as DonationService
    participant Publisher as ApplicationEventPublisher
    participant Context as Spring Context
    participant Listener1 as EmailListener
    participant Listener2 as AuditListener

    Service->>Publisher: publishEvent(DonationEvent)
    Publisher->>Context: Broadcast event

    Context->>Listener1: @EventListener invoked
    Note over Listener1: Send email notification

    Context->>Listener2: @EventListener invoked
    Note over Listener2: Log audit record

    Note over Service,Listener2: Decoupled - Service unaware of listeners

    style Service fill:#0173B2,stroke:#000,color:#fff
    style Publisher fill:#DE8F05,stroke:#000,color:#000
    style Context fill:#029E73,stroke:#000,color:#fff
    style Listener1 fill:#CC78BC,stroke:#000,color:#000
    style Listener2 fill:#CA9161,stroke:#000,color:#fff

Diagram Explanation: This sequence diagram shows Spring’s event-driven architecture - services publish events through ApplicationEventPublisher, and multiple @EventListener methods receive events independently, enabling loose coupling.

Key Takeaways:

  • ApplicationEvent for custom events
  • ApplicationEventPublisher to publish
  • @EventListener to handle events
  • Decouples components

Related Documentation:


Example 64: Scheduled Tasks (Coverage: 96.0%)

Demonstrates scheduled background tasks.

Java Implementation:

import org.springframework.scheduling.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Configuration
@EnableScheduling  // => Enables @Scheduled support
public class SchedulingConfig {
}

@Component
class ScheduledTasks {
    @Scheduled(fixedRate = 5000)
    // => Runs every 5 seconds
    // => Fixed delay between start of executions
    public void reportStatus() {
        System.out.println("Status check at: " + System.currentTimeMillis());
    }

    @Scheduled(cron = "0 0 * * * *")
    // => Cron expression: every hour at minute 0
    // => Format: second minute hour day month weekday
    public void hourlyTask() {
        System.out.println("Hourly task executed");
    }

    @Scheduled(fixedDelay = 3000, initialDelay = 10000)
    // => Waits 10 seconds before first execution
    // => Then runs every 3 seconds after previous completion
    public void delayedTask() {
        System.out.println("Delayed task running");
    }
}

Kotlin Implementation:

import org.springframework.scheduling.annotation.*
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component

@Configuration
@EnableScheduling  // => Enables @Scheduled support
class SchedulingConfig

@Component
class ScheduledTasks {
    @Scheduled(fixedRate = 5000)
    // => Runs every 5 seconds
    // => Fixed delay between start of executions
    fun reportStatus() {
        println("Status check at: ${System.currentTimeMillis()}")
    }

    @Scheduled(cron = "0 0 * * * *")
    // => Cron expression: every hour at minute 0
    // => Format: second minute hour day month weekday
    fun hourlyTask() {
        println("Hourly task executed")
    }

    @Scheduled(fixedDelay = 3000, initialDelay = 10000)
    // => Waits 10 seconds before first execution
    // => Then runs every 3 seconds after previous completion
    fun delayedTask() {
        println("Delayed task running")
    }
}

Key Takeaways:

  • @Scheduled for periodic tasks
  • fixedRate for fixed intervals
  • cron for complex schedules
  • @EnableScheduling required

Related Documentation:


Example 65: Custom Conditional Bean Registration (Coverage: 97.5%)

Demonstrates advanced conditional bean creation.

Java Implementation:

import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class ConditionalConfig {
    @Bean
    @ConditionalOnProperty(
        name = "feature.cache.enabled",
        havingValue = "true"
    )
    // => Bean created only if property = true
    public CacheService cacheService() {
        return new CacheService();
        // => Registered when cache enabled
    }

    @Bean
    @ConditionalOnMissingBean(CacheService.class)
    // => Created only if CacheService bean doesn't exist
    public NoCacheService noCacheService() {
        return new NoCacheService();
        // => Fallback when cache disabled
    }

    @Bean
    @ConditionalOnClass(name = "com.mysql.jdbc.Driver")
    // => Created only if MySQL driver on classpath
    public DatabaseService mysqlService() {
        return new DatabaseService("MySQL");
    }
}

class CacheService {
    public CacheService() {
        System.out.println("CacheService created");
    }
}

class NoCacheService {
    public NoCacheService() {
        System.out.println("NoCacheService created");
    }
}

class DatabaseService {
    public DatabaseService(String type) {
        System.out.println("DatabaseService: " + type);
    }
}

Kotlin Implementation:

import org.springframework.boot.autoconfigure.condition.*
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class ConditionalConfig {
    @Bean
    @ConditionalOnProperty(
        name = ["feature.cache.enabled"],
        havingValue = "true"
    )
    // => Bean created only if property = true
    fun cacheService(): CacheService {
        return CacheService()
        // => Registered when cache enabled
    }

    @Bean
    @ConditionalOnMissingBean(CacheService::class)
    // => Created only if CacheService bean doesn't exist
    fun noCacheService(): NoCacheService {
        return NoCacheService()
        // => Fallback when cache disabled
    }

    @Bean
    @ConditionalOnClass(name = ["com.mysql.jdbc.Driver"])
    // => Created only if MySQL driver on classpath
    fun mysqlService(): DatabaseService {
        return DatabaseService("MySQL")
    }
}

class CacheService {
    init {
        println("CacheService created")
    }
}

class NoCacheService {
    init {
        println("NoCacheService created")
    }
}

class DatabaseService(type: String) {
    init {
        println("DatabaseService: $type")
    }
}

Conditional Bean Registration Flow:

  graph TD
    A[Spring scans @Bean methods] --> B{@Conditional annotations?}
    B -->|@ConditionalOnProperty| C{Property matches?}
    B -->|@ConditionalOnMissingBean| D{Bean exists?}
    B -->|@ConditionalOnClass| E{Class in classpath?}

    C -->|Yes| F[Register bean]
    C -->|No| G[Skip bean]

    D -->|No missing| F
    D -->|Already exists| G

    E -->|Present| F
    E -->|Absent| G

    F --> H[Bean available in context]
    G --> I[Bean not registered]

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#000
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff
    style F fill:#CC78BC,stroke:#000,color:#000
    style G fill:#CA9161,stroke:#000,color:#fff
    style H fill:#0173B2,stroke:#000,color:#fff
    style I fill:#DE8F05,stroke:#000,color:#000

Diagram Explanation: This flow diagram shows how Spring evaluates @Conditional annotations during bean registration, enabling flexible auto-configuration based on properties, existing beans, or classpath contents.

Key Takeaways:

  • @ConditionalOnProperty for property-based registration
  • @ConditionalOnMissingBean for fallbacks
  • @ConditionalOnClass for classpath checks
  • Enables flexible auto-configuration

Related Documentation:


Testing (Examples 66-70)

Example 66: Spring TestContext Framework (Coverage: 99.0%)

Demonstrates integration testing with Spring context.

Java Implementation:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.stereotype.Service;
import static org.junit.jupiter.api.Assertions.*;

@Service
class CalculatorService {
    public int add(int a, int b) {
        return a + b;
    }
}

@SpringBootTest  // => Loads full Spring application context
                 // => All beans available for testing
class CalculatorServiceTest {
    @Autowired  // => Injects actual bean from context
    private CalculatorService calculator;

    @Test
    void testAddition() {
        // => Test with real Spring-managed bean
        int result = calculator.add(5, 3);

        assertEquals(8, result);
        // => Assertion: expected 8
    }
}

Kotlin Implementation:

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.stereotype.Service
import kotlin.test.assertEquals

@Service
class CalculatorService {
    fun add(a: Int, b: Int): Int = a + b
}

@SpringBootTest  // => Loads full Spring application context
                 // => All beans available for testing
class CalculatorServiceTest {
    @Autowired  // => Injects actual bean from context
    private lateinit var calculator: CalculatorService

    @Test
    fun testAddition() {
        // => Test with real Spring-managed bean
        val result = calculator.add(5, 3)

        assertEquals(8, result)
        // => Assertion: expected 8
    }
}

Key Takeaways:

  • @SpringBootTest loads application context
  • @Autowired injects beans into tests
  • Full integration testing
  • Slower than unit tests

Related Documentation:


Example 67: MockMvc for Web Layer Testing (Coverage: 100.0%)

Demonstrates testing Spring MVC controllers.

Java Implementation:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RestController
class GreetingController {
    @GetMapping("/greet")
    public String greet(@RequestParam String name) {
        return "Hello, " + name;
    }
}

@WebMvcTest(GreetingController.class)
// => Loads only web layer (controllers)
// => Faster than @SpringBootTest
class GreetingControllerTest {
    @Autowired
    private MockMvc mockMvc;  // => Mock HTTP client

    @Test
    void testGreeting() throws Exception {
        mockMvc.perform(get("/greet").param("name", "Ali"))
            // => Performs GET /greet?name=Ali
            .andExpect(status().isOk())
            // => Expects HTTP 200
            .andExpect(content().string("Hello, Ali"));
            // => Expects response body
    }
}

Kotlin Implementation:

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.web.bind.annotation.*
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*

@RestController
class GreetingController {
    @GetMapping("/greet")
    fun greet(@RequestParam name: String): String {
        return "Hello, $name"
    }
}

@WebMvcTest(GreetingController::class)
// => Loads only web layer (controllers)
// => Faster than @SpringBootTest
class GreetingControllerTest {
    @Autowired
    private lateinit var mockMvc: MockMvc  // => Mock HTTP client

    @Test
    fun testGreeting() {
        mockMvc.perform(get("/greet").param("name", "Ali"))
            // => Performs GET /greet?name=Ali
            .andExpect(status().isOk)
            // => Expects HTTP 200
            .andExpect(content().string("Hello, Ali"))
            // => Expects response body
    }
}

Key Takeaways:

  • @WebMvcTest for controller tests
  • MockMvc simulates HTTP requests
  • Test request/response without server
  • Faster than full integration tests

Related Documentation:


Example 68: @Transactional in Tests (Coverage: 100.0%)

Demonstrates transactional test rollback.

Java Implementation:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional  // => Each test runs in transaction
                // => Automatically rolled back after test
class DatabaseTest {
    @Autowired
    private JdbcTemplate jdbc;

    @Test
    void testInsert() {
        jdbc.update("INSERT INTO donations (donor, amount) VALUES (?, ?)",
                    "TestUser", 100.0);
        // => Inserts data

        Integer count = jdbc.queryForObject(
            "SELECT COUNT(*) FROM donations WHERE donor = ?",
            Integer.class,
            "TestUser"
        );

        assertEquals(1, count);
        // => Assertion passes

        // => Transaction rolled back after test
        // => Database unchanged
    }
}

Kotlin Implementation:

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.transaction.annotation.Transactional
import kotlin.test.assertEquals

@SpringBootTest
@Transactional  // => Each test runs in transaction
                // => Automatically rolled back after test
class DatabaseTest {
    @Autowired
    private lateinit var jdbc: JdbcTemplate

    @Test
    fun testInsert() {
        jdbc.update("INSERT INTO donations (donor, amount) VALUES (?, ?)",
                    "TestUser", 100.0)
        // => Inserts data

        val count = jdbc.queryForObject(
            "SELECT COUNT(*) FROM donations WHERE donor = ?",
            Int::class.java,
            "TestUser"
        )

        assertEquals(1, count)
        // => Assertion passes

        // => Transaction rolled back after test
        // => Database unchanged
    }
}

Key Takeaways:

  • @Transactional on test class
  • Automatic rollback after each test
  • Database remains clean
  • Enables repeatable tests

Related Documentation:


Example 69: Test Profiles (Coverage: 100.0%)

Demonstrates test-specific configuration.

Java Implementation:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.*;

// application-test.properties:
// test.value=test-data

@SpringBootTest
@ActiveProfiles("test")  // => Activates "test" profile
                         // => Loads application-test.properties
class ProfileTest {
    @Value("${test.value}")
    private String testValue;  // => Reads from test profile

    @Test
    void testProfileValue() {
        assertEquals("test-data", testValue);
        // => Uses test-specific configuration
    }
}

Kotlin Implementation:

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import kotlin.test.assertEquals

// application-test.properties:
// test.value=test-data

@SpringBootTest
@ActiveProfiles("test")  // => Activates "test" profile
                         // => Loads application-test.properties
class ProfileTest {
    @Value("\${test.value}")
    private lateinit var testValue: String  // => Reads from test profile

    @Test
    fun testProfileValue() {
        assertEquals("test-data", testValue)
        // => Uses test-specific configuration
    }
}

Key Takeaways:

  • @ActiveProfiles for test configuration
  • application-test.properties for test data
  • Separate test/production config
  • H2 in-memory DB common for tests

Related Documentation:


Example 70: Mocking with @MockBean (Coverage: 100.0%)

Demonstrates mocking dependencies in tests.

Java Implementation:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.stereotype.Service;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@Service
class DataService {
    public String fetchData() {
        return "real data";
    }
}

@Service
class BusinessService {
    private final DataService dataService;

    public BusinessService(DataService dataService) {
        this.dataService = dataService;
    }

    public String process() {
        return "Processed: " + dataService.fetchData();
    }
}

@SpringBootTest
class MockBeanTest {
    @MockBean  // => Replaces real bean with mock
    private DataService dataService;

    @Autowired  // => Injects BusinessService with mocked dependency
    private BusinessService businessService;

    @Test
    void testWithMock() {
        when(dataService.fetchData()).thenReturn("mock data");
        // => Configures mock behavior

        String result = businessService.process();

        assertEquals("Processed: mock data", result);
        // => Uses mock instead of real service
    }
}

Kotlin Implementation:

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.stereotype.Service
import org.mockito.Mockito.*
import kotlin.test.assertEquals

@Service
class DataService {
    fun fetchData(): String = "real data"
}

@Service
class BusinessService(private val dataService: DataService) {
    fun process(): String = "Processed: ${dataService.fetchData()}"
}

@SpringBootTest
class MockBeanTest {
    @MockBean  // => Replaces real bean with mock
    private lateinit var dataService: DataService

    @Autowired  // => Injects BusinessService with mocked dependency
    private lateinit var businessService: BusinessService

    @Test
    fun testWithMock() {
        `when`(dataService.fetchData()).thenReturn("mock data")
        // => Configures mock behavior

        val result = businessService.process()

        assertEquals("Processed: mock data", result)
        // => Uses mock instead of real service
    }
}

Key Takeaways:

  • @MockBean replaces bean with mock
  • Mockito for behavior configuration
  • Test without external dependencies
  • Fast, isolated unit tests

Related Documentation:


Production Patterns (Examples 71-75)

Example 71: Custom Health Indicator (Coverage: 100.0%)

Demonstrates application health monitoring.

Java Implementation:

import org.springframework.boot.actuate.health.*;
import org.springframework.stereotype.Component;

@Component
class CustomHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        // => Called by /actuator/health endpoint
        // => Returns application health status

        boolean healthy = checkSystemHealth();

        if (healthy) {
            return Health.up()
                .withDetail("message", "System healthy")
                .withDetail("uptime", getUptime())
                .build();
            // => Status: UP
        } else {
            return Health.down()
                .withDetail("error", "System degraded")
                .build();
            // => Status: DOWN
        }
    }

    private boolean checkSystemHealth() {
        return true;  // => Actual health check logic
    }

    private long getUptime() {
        return System.currentTimeMillis();
    }
}

Kotlin Implementation:

import org.springframework.boot.actuate.health.*
import org.springframework.stereotype.Component

@Component
class CustomHealthIndicator : HealthIndicator {
    override fun health(): Health {
        // => Called by /actuator/health endpoint
        // => Returns application health status

        val healthy = checkSystemHealth()

        return if (healthy) {
            Health.up()
                .withDetail("message", "System healthy")
                .withDetail("uptime", getUptime())
                .build()
            // => Status: UP
        } else {
            Health.down()
                .withDetail("error", "System degraded")
                .build()
            // => Status: DOWN
        }
    }

    private fun checkSystemHealth(): Boolean = true  // => Actual health check logic

    private fun getUptime(): Long = System.currentTimeMillis()
}

Key Takeaways:

  • HealthIndicator for custom health checks
  • Exposed via Spring Boot Actuator
  • Health.up() or Health.down() status
  • Include diagnostic details

Related Documentation:


Example 72: Custom Metrics (Coverage: 100.0%)

Demonstrates application metrics tracking.

Java Implementation:

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;

@Service
class DonationMetricsService {
    private final Counter donationCounter;

    public DonationMetricsService(MeterRegistry registry) {
        // => MeterRegistry auto-configured by Spring Boot

        this.donationCounter = Counter.builder("donations.total")
            // => Metric name
            .tag("type", "charity")
            // => Tag for filtering/grouping
            .description("Total donations received")
            // => Metric description
            .register(registry);
        // => Registers metric
    }

    public void recordDonation(double amount) {
        donationCounter.increment();
        // => Increments counter
        // => Exposed at /actuator/metrics/donations.total

        System.out.println("Donation recorded: $" + amount);
    }
}

Kotlin Implementation:

import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.MeterRegistry
import org.springframework.stereotype.Service

@Service
class DonationMetricsService(registry: MeterRegistry) {
    // => MeterRegistry auto-configured by Spring Boot

    private val donationCounter: Counter = Counter.builder("donations.total")
        // => Metric name
        .tag("type", "charity")
        // => Tag for filtering/grouping
        .description("Total donations received")
        // => Metric description
        .register(registry)
    // => Registers metric

    fun recordDonation(amount: Double) {
        donationCounter.increment()
        // => Increments counter
        // => Exposed at /actuator/metrics/donations.total

        println("Donation recorded: $$amount")
    }
}

Key Takeaways:

  • Micrometer for metrics
  • Counter, Gauge, Timer available
  • Tags for dimensional metrics
  • Exposed via Actuator

Related Documentation:


Example 73: Request/Response Logging Interceptor (Coverage: 100.0%)

Demonstrates HTTP request interception.

Java Implementation:

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.*;

@Component
class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler
    ) {
        // => Called BEFORE controller method
        System.out.println("Request: " + request.getMethod() + " " +
                          request.getRequestURI());
        return true;  // => Continue to controller (false = abort)
    }

    @Override
    public void afterCompletion(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler,
        Exception ex
    ) {
        // => Called AFTER response sent
        System.out.println("Response status: " + response.getStatus());
    }
}

@Configuration
class WebConfig implements WebMvcConfigurer {
    private final LoggingInterceptor loggingInterceptor;

    public WebConfig(LoggingInterceptor loggingInterceptor) {
        this.loggingInterceptor = loggingInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor)
            .addPathPatterns("/api/**");
        // => Apply to /api/** paths only
    }
}

Kotlin Implementation:

import org.springframework.stereotype.Component
import org.springframework.web.servlet.HandlerInterceptor
import org.springframework.web.servlet.config.annotation.*
import org.springframework.context.annotation.Configuration
import javax.servlet.http.*

@Component
class LoggingInterceptor : HandlerInterceptor {
    override fun preHandle(
        request: HttpServletRequest,
        response: HttpServletResponse,
        handler: Any
    ): Boolean {
        // => Called BEFORE controller method
        println("Request: ${request.method} ${request.requestURI}")
        return true  // => Continue to controller (false = abort)
    }

    override fun afterCompletion(
        request: HttpServletRequest,
        response: HttpServletResponse,
        handler: Any,
        ex: Exception?
    ) {
        // => Called AFTER response sent
        println("Response status: ${response.status}")
    }
}

@Configuration
class WebConfig(
    private val loggingInterceptor: LoggingInterceptor
) : WebMvcConfigurer {
    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(loggingInterceptor)
            .addPathPatterns("/api/**")
        // => Apply to /api/** paths only
    }
}

Key Takeaways:

  • HandlerInterceptor for request/response interception
  • preHandle() before controller
  • afterCompletion() after response
  • Configure via WebMvcConfigurer

Related Documentation:


Example 74: Connection Pooling with HikariCP (Coverage: 100.0%)

Demonstrates database connection pooling configuration.

Java Implementation:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();

        config.setJdbcUrl("jdbc:h2:mem:testdb");
        // => Database URL

        config.setUsername("sa");
        config.setPassword("");

        config.setMaximumPoolSize(10);
        // => Maximum 10 connections in pool

        config.setMinimumIdle(2);
        // => Keep at least 2 idle connections

        config.setConnectionTimeout(30000);
        // => Wait max 30 seconds for connection

        config.setIdleTimeout(600000);
        // => Close idle connections after 10 minutes

        config.setMaxLifetime(1800000);
        // => Recycle connections after 30 minutes

        return new HikariDataSource(config);
        // => Returns pooled DataSource
        // => Spring uses this for all database operations
    }
}

Kotlin Implementation:

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.sql.DataSource

@Configuration
class DataSourceConfig {
    @Bean
    fun dataSource(): DataSource {
        val config = HikariConfig().apply {
            jdbcUrl = "jdbc:h2:mem:testdb"
            // => Database URL

            username = "sa"
            password = ""

            maximumPoolSize = 10
            // => Maximum 10 connections in pool

            minimumIdle = 2
            // => Keep at least 2 idle connections

            connectionTimeout = 30000
            // => Wait max 30 seconds for connection

            idleTimeout = 600000
            // => Close idle connections after 10 minutes

            maxLifetime = 1800000
            // => Recycle connections after 30 minutes
        }

        return HikariDataSource(config)
        // => Returns pooled DataSource
        // => Spring uses this for all database operations
    }
}

Key Takeaways:

  • HikariCP default connection pool in Spring Boot
  • Configure pool size, timeouts, lifecycle
  • Connection reuse improves performance
  • Spring Boot auto-configures if properties set

Related Documentation:


Example 75: Custom Validation Annotation (Coverage: 100.0%)

Demonstrates creating custom validation constraint.

Java Implementation:

import javax.validation.*;
import javax.validation.constraints.*;
import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ZakatAmountValidator.class)
// => Links annotation to validator implementation
@interface ValidZakatAmount {
    String message() default "Invalid zakat amount";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

class ZakatAmountValidator implements ConstraintValidator<ValidZakatAmount, Double> {
    @Override
    public boolean isValid(Double value, ConstraintValidatorContext context) {
        // => Validation logic
        if (value == null) {
            return true;  // => Let @NotNull handle null check
        }

        return value >= 0 && value <= 100000;
        // => Amount must be 0-100000
    }
}

class DonationForm {
    @ValidZakatAmount(message = "Zakat amount must be 0-100000")
    // => Uses custom validator
    private Double amount;

    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
}

Kotlin Implementation:

import javax.validation.*
import javax.validation.constraints.*

@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [ZakatAmountValidator::class])
// => Links annotation to validator implementation
annotation class ValidZakatAmount(
    val message: String = "Invalid zakat amount",
    val groups: Array<KClass<*>> = [],
    val payload: Array<KClass<out Payload>> = []
)

class ZakatAmountValidator : ConstraintValidator<ValidZakatAmount, Double> {
    override fun isValid(value: Double?, context: ConstraintValidatorContext): Boolean {
        // => Validation logic
        if (value == null) {
            return true  // => Let @NotNull handle null check
        }

        return value in 0.0..100000.0
        // => Amount must be 0-100000
    }
}

data class DonationForm(
    @field:ValidZakatAmount(message = "Zakat amount must be 0-100000")
    // => Uses custom validator
    val amount: Double?
)

Key Takeaways:

  • Create custom validation annotations
  • Implement ConstraintValidator interface
  • Reusable across application
  • Works with Bean Validation (@Valid)

Related Documentation:


Summary

This advanced tutorial covered 25 Spring Framework examples (51-75) achieving 75-95% coverage:

REST APIs (51-55):

  • ResponseEntity, content negotiation, CORS, versioning, global exception handling

Security (56-60):

  • Security configuration, method security, UserDetailsService, JWT, password encoding

Advanced Patterns (61-65):

  • Caching, async execution, application events, scheduling, conditional beans

Testing (66-70):

  • TestContext, MockMvc, transactional tests, test profiles, mocking

Production (71-75):

  • Health indicators, metrics, interceptors, connection pooling, custom validation

Congratulations! You’ve completed the full Spring Framework by-example series covering 75 examples across beginner, intermediate, and advanced levels, achieving 95% framework coverage.

Next Steps:

  • Build production applications with Spring Boot
  • Explore Spring Cloud for microservices
  • Study Spring Data JPA for ORM
  • Learn Spring Security advanced features
  • Practice with real-world Islamic finance applications
Last updated