Good API documentation is non-negotiable. Springdoc reads your Spring MVC code and auto-generates interactive OpenAPI 3.1 documentation — no separate doc files to maintain. Setup <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.5.0</version> </dependency> Start the app and visit: Swagger UI: http://localhost:8080/swagger-ui.html OpenAPI JSON: http://localhost:8080/v3/api-docs OpenAPI YAML: http://localhost:8080/v3/api-docs.yaml Springdoc scans your @RestController classes and generates the spec automatically. Zero configuration needed for basic docs. Configuring the API Info @Configuration public class OpenApiConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() .
Continue reading »Spring Boot Tutorial
59 posts in this section
API Gateway with Spring Cloud Gateway
The API gateway is the single entry point for all client traffic. It handles routing to downstream services, authentication, rate limiting, and request/response transformation — so individual services don’t have to. Setup <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> Spring Cloud Gateway is reactive (WebFlux-based) — don’t include spring-boot-starter-web. Route Configuration YAML Configuration spring: application: name: api-gateway cloud: gateway: routes: - id: order-service uri: lb://order-service # lb:// = load-balanced via Eureka predicates: - Path=/api/orders/** filters: - StripPrefix=0 # don't strip path prefix - AddRequestHeader=X-Gateway-Source, api-gateway - id: customer-service uri: lb://customer-service predicates: - Path=/api/customers/** filters: - StripPrefix=0 - id: payment-service uri: lb://payment-service predicates: - Path=/api/payments/** filters: - StripPrefix=0 # Versioned routing - id: order-service-v2 uri: lb://order-service-v2 predicates: - Path=/api/v2/orders/** - Header=X-API-Version, 2 default-filters: - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin Programmatic Route Configuration @Configuration public class GatewayRouteConfig { @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.
Continue reading »API Versioning in Spring Boot 4
APIs evolve. Adding fields is safe — removing or changing fields breaks clients. Versioning gives you a path to evolve the API without breaking existing integrations. When You Need Versioning You need a new API version when: Removing a field from a response Changing a field’s type or semantics Changing URL structure significantly Breaking backward-incompatible business logic changes You don’t need a new version for: Adding new optional fields (non-breaking) Adding new endpoints Bug fixes that don’t change the contract Strategy 1: URL Path Versioning The most common and explicit approach:
Continue reading »Application Configuration: Properties, YAML, and Profiles
Every real application needs different configuration for different environments — a local database for dev, a connection pool for staging, a secret manager for prod. This article covers everything Spring Boot gives you to handle this cleanly. application.properties vs application.yml Spring Boot reads configuration from src/main/resources/application.properties (or .yml) by default. Both formats express the same thing: application.properties: server.port=8080 spring.datasource.url=jdbc:postgresql://localhost:5432/orders spring.datasource.username=app spring.datasource.password=secret spring.jpa.show-sql=false application.yml: server: port: 8080 spring: datasource: url: jdbc:postgresql://localhost:5432/orders username: app password: secret jpa: show-sql: false YAML is generally preferred for nested properties — it’s less repetitive.
Continue reading »Async Processing with @Async and Virtual Threads
Not every operation needs to complete before the response returns. Sending an email, generating a report, publishing an event — these can run in the background. Async processing keeps request latency low while the work continues. @Async — Fire and Forget @SpringBootApplication @EnableAsync public class OrderServiceApplication { } @Service @Slf4j public class NotificationService { @Async // runs in a separate thread public void sendOrderConfirmation(Order order) { log.info("Sending confirmation for order {}", order.
Continue reading »Bean Validation: @Valid, Custom Validators, and Error Messages
Every request that enters your API needs validation. Without it, invalid data propagates through your application and produces confusing errors deep in the stack. This article covers how to validate data at the API boundary using Jakarta Bean Validation. Setup <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> This includes Hibernate Validator — the reference implementation of Jakarta Bean Validation 3.0. Built-in Constraints Annotate fields in your DTO with constraint annotations: public record CreateOrderRequest( @NotNull(message = "Customer ID is required") UUID customerId, @NotEmpty(message = "Order must contain at least one item") @Size(max = 50, message = "Order cannot have more than {max} items") List<@Valid OrderItemRequest> items, @Valid ShippingAddressRequest shippingAddress, @Size(max = 20, message = "Promo code cannot exceed {max} characters") String promoCode ) {} public record OrderItemRequest( @NotNull UUID productId, @Positive(message = "Quantity must be positive") @Max(value = 999, message = "Cannot order more than {value} units of a product") int quantity, @NotNull @Positive BigDecimal unitPrice ) {} public record ShippingAddressRequest( @NotBlank(message = "Address line 1 is required") @Size(max = 100) String line1, @Size(max = 100) String line2, @NotBlank @Size(max = 50) String city, @NotBlank @Pattern(regexp = "[A-Z]{2}", message = "Country must be a 2-letter ISO code") String country, @NotBlank @Pattern(regexp = "\\w{3,10}", message = "Invalid postal code format") String postalCode ) {} Common Constraint Annotations Annotation Validates @NotNull Value is not null @NotEmpty String/collection not null and not empty @NotBlank String not null, not empty, not just whitespace @Size(min, max) String length or collection size @Min(value) Number ≥ value @Max(value) Number ≤ value @Positive Number > 0 @PositiveOrZero Number ≥ 0 @Negative Number < 0 @Pattern(regexp) String matches regex @Email Valid email format @Past Date is in the past @Future Date is in the future @DecimalMin(value) Decimal ≥ value (as string) @AssertTrue Boolean is true @AssertFalse Boolean is false Triggering Validation with @Valid Add @Valid to the controller parameter to trigger validation:
Continue reading »Building a Modular Monolith with Spring Modulith
Microservices solve organizational and scalability problems — but they add operational complexity. Most applications don’t need that complexity. A modular monolith gives you clean boundaries and loose coupling without the distributed systems overhead. Spring Modulith enforces those boundaries. The Problem with Unstructured Monoliths Without explicit boundaries, every part of the codebase can talk to every other part: // OrderService calling PaymentRepository directly — skipping the Payment module @Service public class OrderService { @Autowired PaymentRepository paymentRepository; // ← wrong @Autowired NotificationService notificationService; // ← wrong @Autowired AnalyticsService analyticsService; // ← wrong } This creates hidden coupling.
Continue reading »Building Your First REST API with Spring Boot
Time to build something real. In this article you’ll create a fully functional REST API for the order-service — create, read, update, and delete orders over HTTP. Project Setup Start with these dependencies at start.spring.io: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> spring-boot-starter-web includes: Embedded Tomcat (no WAR deployment needed) Spring MVC (the web framework) Jackson (JSON serialization) The Request Processing Pipeline Before writing code, understand how Spring MVC handles a request:
Continue reading »Caching with Caffeine and Redis
Caching sits between your application and the database. A cache hit returns data in microseconds; a database query takes milliseconds. For frequently-read, infrequently-changed data, caching is the highest-leverage performance improvement. Spring Cache Abstraction Spring’s cache abstraction lets you add caching with annotations — the backing store (Caffeine, Redis, Hazelcast) is swappable: @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository repository; @Cacheable("products") // cache the result public Product findById(UUID id) { return repository.
Continue reading »Centralized Configuration with Spring Cloud Config Server
Managing configuration for 10 services across 3 environments means 30 separate config files. Spring Cloud Config Server centralizes all of them — one place to change a database URL, one place to rotate secrets, and services pick up changes without redeployment. Config Server Setup <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } } # application.yml — config-server server: port: 8888 spring: application: name: config-server cloud: config: server: git: uri: https://github.
Continue reading »