<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tutorial on Devops Monk</title><link>https://devops-monk.com/tags/tutorial/</link><description>Recent content in Tutorial on Devops Monk</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 03 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://devops-monk.com/tags/tutorial/index.xml" rel="self" type="application/rss+xml"/><item><title>API Documentation with OpenAPI and Springdoc</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-openapi-springdoc/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-openapi-springdoc/</guid><description>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 &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springdoc&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;springdoc-openapi-starter-webmvc-ui&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;2.5.0&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; 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() .</description></item><item><title>API Gateway with Spring Cloud Gateway</title><link>https://devops-monk.com/tutorials/spring-boot/spring-cloud-gateway/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-cloud-gateway/</guid><description>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&amp;rsquo;t have to.
Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-cloud-starter-gateway&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-cloud-starter-netflix-eureka-client&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; Spring Cloud Gateway is reactive (WebFlux-based) — don&amp;rsquo;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&amp;#39;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.</description></item><item><title>API Versioning in Spring Boot 4</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-api-versioning/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-api-versioning/</guid><description>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&amp;rsquo;s type or semantics Changing URL structure significantly Breaking backward-incompatible business logic changes You don&amp;rsquo;t need a new version for:
Adding new optional fields (non-breaking) Adding new endpoints Bug fixes that don&amp;rsquo;t change the contract Strategy 1: URL Path Versioning The most common and explicit approach:</description></item><item><title>Application Configuration: Properties, YAML, and Profiles</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-configuration-profiles/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-configuration-profiles/</guid><description>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&amp;rsquo;s less repetitive.</description></item><item><title>Async Processing with @Async and Virtual Threads</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-async-virtual-threads/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-async-virtual-threads/</guid><description>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(&amp;#34;Sending confirmation for order {}&amp;#34;, order.</description></item><item><title>Bean Validation: @Valid, Custom Validators, and Error Messages</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-bean-validation/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-bean-validation/</guid><description>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 &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-validation&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; 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 = &amp;#34;Customer ID is required&amp;#34;) UUID customerId, @NotEmpty(message = &amp;#34;Order must contain at least one item&amp;#34;) @Size(max = 50, message = &amp;#34;Order cannot have more than {max} items&amp;#34;) List&amp;lt;@Valid OrderItemRequest&amp;gt; items, @Valid ShippingAddressRequest shippingAddress, @Size(max = 20, message = &amp;#34;Promo code cannot exceed {max} characters&amp;#34;) String promoCode ) {} public record OrderItemRequest( @NotNull UUID productId, @Positive(message = &amp;#34;Quantity must be positive&amp;#34;) @Max(value = 999, message = &amp;#34;Cannot order more than {value} units of a product&amp;#34;) int quantity, @NotNull @Positive BigDecimal unitPrice ) {} public record ShippingAddressRequest( @NotBlank(message = &amp;#34;Address line 1 is required&amp;#34;) @Size(max = 100) String line1, @Size(max = 100) String line2, @NotBlank @Size(max = 50) String city, @NotBlank @Pattern(regexp = &amp;#34;[A-Z]{2}&amp;#34;, message = &amp;#34;Country must be a 2-letter ISO code&amp;#34;) String country, @NotBlank @Pattern(regexp = &amp;#34;\\w{3,10}&amp;#34;, message = &amp;#34;Invalid postal code format&amp;#34;) 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 &amp;gt; 0 @PositiveOrZero Number ≥ 0 @Negative Number &amp;lt; 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:</description></item><item><title>Building a Modular Monolith with Spring Modulith</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-spring-modulith/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-spring-modulith/</guid><description>Microservices solve organizational and scalability problems — but they add operational complexity. Most applications don&amp;rsquo;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.</description></item><item><title>Building Your First REST API with Spring Boot</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-rest-api/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-rest-api/</guid><description>Time to build something real. In this article you&amp;rsquo;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:
&amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-validation&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; 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:</description></item><item><title>Caching with Caffeine and Redis</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-caching/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-caching/</guid><description>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&amp;rsquo;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(&amp;#34;products&amp;#34;) // cache the result public Product findById(UUID id) { return repository.</description></item><item><title>Centralized Configuration with Spring Cloud Config Server</title><link>https://devops-monk.com/tutorials/spring-boot/spring-cloud-config-server/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-cloud-config-server/</guid><description>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 &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-cloud-config-server&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; @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.</description></item><item><title>Changelog Formats: XML, YAML, JSON, and SQL — When to Use Each</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-changelog-formats/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-changelog-formats/</guid><description>Liquibase supports four changelog formats: XML, YAML, JSON, and SQL. The format you pick affects readability, tooling support, and what features are available. This article shows the same changeset in all four formats so you can compare them directly — then explains when each format is the right choice.
The Same Change in All Four Formats To make comparison concrete, here is a single changeset — creating the users table from the e-commerce schema — written in every format.</description></item><item><title>Changelog Organization: Master Files, include/includeAll, Directory Structures</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-changelog-organization/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-changelog-organization/</guid><description>A single changelog file works fine for a tutorial. It stops working the moment two developers add migrations on the same day, or when you need to find the changeset that introduced a column added six months ago, or when a feature branch&amp;rsquo;s migrations must be reviewed independently before merging.
Changelog organization is not a polish step — it is what determines whether Liquibase stays manageable at scale or turns into a source of merge conflicts and mystery failures.</description></item><item><title>Common Migration Patterns: 12 Real-World Schema Changes with MySQL</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-migration-patterns/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-migration-patterns/</guid><description>Most Liquibase guides explain change types by listing them. This article is different — it walks through twelve patterns you will encounter repeatedly in a real project, explains the MySQL-specific behaviour of each, and shows the rollback you need to write alongside every change.
All examples build on the ecommerce database established in Part 1. By the end, you will have a complete reference you can reach for on any working day.</description></item><item><title>Contexts and Labels: Multi-Environment Filtering</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-contexts-labels/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-contexts-labels/</guid><description>The most common multi-environment problem in database migrations: seed data that should run in dev and staging but must never touch production. The naive solution is maintaining separate changelogs per environment. The correct solution is contexts and labels — Liquibase&amp;rsquo;s built-in filtering mechanism that lets a single changelog serve every environment.
This article covers both features, explains the critical difference between them, and shows exactly how to wire them into Spring Boot profiles.</description></item><item><title>Core Commands: update, rollback, status, history, validate, diff</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-core-commands/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-core-commands/</guid><description>Liquibase has over 50 commands. In practice, you will use fewer than ten of them for 95% of your work. This article covers those ten commands — what each one does, when to reach for it, and what the output tells you. Every example builds on the ecommerce database and users table from Article 4.
The commands are organized by what you&amp;rsquo;re trying to accomplish: inspect, apply, undo, and verify.</description></item><item><title>Core Concepts: Changelog, Changeset, and Tracking Tables</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-core-concepts/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-core-concepts/</guid><description>Before writing a single migration, you need the mental model. Understanding how Liquibase thinks about changelogs, changesets, and identity prevents the most common mistakes — ones that are painful to fix after deployment.
The Changelog The changelog is the file Liquibase reads. It contains an ordered list of changesets. Think of it as your database&amp;rsquo;s Git history — a sequential record of every change ever made.
# db/changelog/db.changelog-master.yaml databaseChangeLog: - changeSet: id: &amp;#34;20240101-001&amp;#34; author: abhay changes: - createTable: tableName: users columns: - column: name: id type: BIGINT autoIncrement: true constraints: primaryKey: true nullable: false - changeSet: id: &amp;#34;20240101-002&amp;#34; author: abhay changes: - addColumn: tableName: users columns: - column: name: email type: VARCHAR(255) constraints: nullable: false unique: true The changelog format (YAML, XML, SQL) doesn&amp;rsquo;t matter for understanding the concept — it&amp;rsquo;s always a list of changesets in order.</description></item><item><title>CRUD Operations with JpaRepository</title><link>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-crud/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-crud/</guid><description>You have entities and repositories set up. Now let&amp;rsquo;s work through every data operation in depth — create, read, update, delete — and the JPA mechanics behind each.
Create: save() @Service @RequiredArgsConstructor @Transactional public class OrderService { private final OrderRepository orderRepository; public Order createOrder(CreateOrderRequest request) { Order order = new Order(); order.setCustomerId(request.customerId()); order.setOrderNumber(generateOrderNumber()); order.setStatus(OrderStatus.PENDING); request.items().forEach(itemReq -&amp;gt; { OrderItem item = new OrderItem(); item.setProductId(itemReq.productId()); item.setQuantity(itemReq.quantity()); item.setUnitPrice(itemReq.unitPrice()); order.addItem(item); // manages bidirectional relationship }); return orderRepository.</description></item><item><title>Database Migrations with Flyway</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-flyway-migrations/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-flyway-migrations/</guid><description>Never use spring.jpa.hibernate.ddl-auto=update in production. It&amp;rsquo;s unpredictable, irreversible, and can corrupt data. Flyway gives you version-controlled, audited, reproducible schema changes.
Why Flyway? Every database change runs as a versioned SQL script. Flyway tracks which scripts have run in a flyway_schema_history table. When the app starts:
Flyway reads all migration files Checks which have already run (by checking the history table) Runs any new ones, in order If the current state doesn&amp;rsquo;t match the expected state → fails fast Benefits:</description></item><item><title>Dockerizing Spring Boot Applications</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-docker/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-docker/</guid><description>Packaging your Spring Boot application as a Docker container is the standard way to deploy it — to Kubernetes, cloud platforms, or any container runtime. This article covers building production-quality images.
The Naive Dockerfile (Don&amp;rsquo;t Use This) FROM eclipse-temurin:21-jdk COPY target/order-service.jar app.jar ENTRYPOINT [&amp;#34;java&amp;#34;, &amp;#34;-jar&amp;#34;, &amp;#34;app.jar&amp;#34;] Problems:
600MB+ image (JDK, not JRE) No layer caching — every code change rebuilds the whole JAR layer Runs as root (security risk) No health check Layered JARs (Better Cache Utilization) Spring Boot 3 creates layered JARs by default.</description></item><item><title>DTOs and Response Shaping</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-dto-response/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-dto-response/</guid><description>Every beginner makes the same mistake: returning JPA entities directly from REST controllers. This article explains why that&amp;rsquo;s dangerous, and how to design clean DTOs that make your API stable, secure, and maintainable.
Why Not Return Entities Directly? Consider this:
@GetMapping(&amp;#34;/{id}&amp;#34;) public Order getOrder(@PathVariable UUID id) { return orderRepository.findById(id).orElseThrow(); // Entity returned directly } Problems with this:
1. Serialization of lazy-loaded relationships crashes
@Entity public class Order { @OneToMany(fetch = FetchType.</description></item><item><title>Entity Relationships: @OneToMany, @ManyToOne, @ManyToMany</title><link>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-relationships/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-relationships/</guid><description>Relationships are the trickiest part of JPA. A wrong cascade type or a missing mappedBy causes subtle bugs that appear in production. This article covers every relationship type with real examples and the pitfalls to avoid.
Relationship Fundamentals JPA relationships can be:
Direction: Unidirectional (one side knows about the other) or Bidirectional (both sides know each other) Cardinality: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany Fetch: LAZY (load on access) or EAGER (load immediately) Ownership: The side with the foreign key column is the owner Default fetch types:</description></item><item><title>Externalized Configuration with @ConfigurationProperties</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-configuration-properties/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-configuration-properties/</guid><description>@ConfigurationProperties binds external configuration to a typed Java class — replacing scattered @Value annotations with a single, validated, testable object.
Why @ConfigurationProperties Over @Value // @Value — scattered, no type safety, no validation @Service public class PaymentService { @Value(&amp;#34;${payment.gateway.url}&amp;#34;) private String gatewayUrl; @Value(&amp;#34;${payment.gateway.timeout:5000}&amp;#34;) private int timeoutMs; @Value(&amp;#34;${payment.gateway.api-key}&amp;#34;) private String apiKey; @Value(&amp;#34;${payment.gateway.max-retries:3}&amp;#34;) private int maxRetries; } // @ConfigurationProperties — one place, typed, validated @Service public class PaymentService { private final PaymentProperties properties; // all config in one place, injected as a single object } @ConfigurationProperties gives you:</description></item><item><title>Full Observability: Prometheus + Grafana + Tempo + Loki</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-observability/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-observability/</guid><description>Observability means being able to answer &amp;ldquo;what&amp;rsquo;s wrong and why&amp;rdquo; from the outside — without modifying the code. The three pillars: metrics (what happened), logs (what the code did), and traces (how a request flowed). This article wires them all together.
The Stack Spring Boot App ├── Metrics → Micrometer → Prometheus scrape → Grafana dashboards ├── Traces → Micrometer Tracing → OTLP → Tempo → Grafana trace view └── Logs → Logback → Loki4j → Loki → Grafana log explorer All three converge in Grafana — click a metric spike to see the correlated logs and traces for that exact time window.</description></item><item><title>Global Exception Handling with @ControllerAdvice and ProblemDetail</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-exception-handling/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-exception-handling/</guid><description>Without global exception handling, Spring returns raw stack traces or inconsistent error shapes. This article shows how to centralize all error handling in one place and return structured, RFC 7807-compliant responses.
The Problem Without Global Handling Default Spring Boot error responses are inconsistent:
// Validation failure (MethodArgumentNotValidException) { &amp;#34;timestamp&amp;#34;: &amp;#34;2026-05-03T10:00:00.000+00:00&amp;#34;, &amp;#34;status&amp;#34;: 400, &amp;#34;error&amp;#34;: &amp;#34;Bad Request&amp;#34;, &amp;#34;path&amp;#34;: &amp;#34;/api/orders&amp;#34; } // Details of which fields failed? Not included. // Custom exception not handled // → 500 Internal Server Error with a stack trace in the body (in dev mode) What clients actually need:</description></item><item><title>GraalVM Native Images: Millisecond Startup</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-graalvm-native/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-graalvm-native/</guid><description>A regular Spring Boot application takes 2–10 seconds to start. A GraalVM native image of the same application starts in under 100 milliseconds. For serverless functions, batch jobs, and CLI tools, this is the difference between viable and unusable.
What Is a Native Image? GraalVM&amp;rsquo;s native image compiler performs ahead-of-time (AOT) compilation. Instead of shipping a JAR that the JVM interprets at runtime, you ship a standalone executable that:
Contains only the code your application actually uses Has no JVM startup overhead Uses much less memory (no JIT compiler, no class metadata) Starts in milliseconds The tradeoff: compile time increases from seconds to minutes.</description></item><item><title>Graceful Shutdown and Production Readiness</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-production-readiness/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-production-readiness/</guid><description>An application that starts and serves traffic is not production-ready. Production readiness means it shuts down cleanly, handles spikes, recovers from transient failures, and gives you visibility into what it&amp;rsquo;s doing. This article covers the operational layer.
Graceful Shutdown When Kubernetes terminates a pod, it sends SIGTERM. Without graceful shutdown, in-flight requests are killed mid-execution — users see 500 errors or dropped writes.
Enable graceful shutdown:
server: shutdown: graceful # wait for in-flight requests to complete spring: lifecycle: timeout-per-shutdown-phase: 30s # max wait before forcing shutdown With this configured, Spring Boot:</description></item><item><title>Handling Requests: Path Variables, Query Params, and Request Bodies</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-request-handling/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-request-handling/</guid><description>A REST API receives data in many places: URL path, query string, body, headers, cookies. This article covers every way to extract that data in Spring MVC.
@PathVariable — Extract from URL Use @PathVariable when the data is part of the URL path:
// URL: GET /api/orders/550e8400-e29b-41d4-a716-446655440000 @GetMapping(&amp;#34;/{id}&amp;#34;) public OrderResponse getOrder(@PathVariable UUID id) { return orderService.findById(id) .map(OrderResponse::from) .orElseThrow(() -&amp;gt; new OrderNotFoundException(id)); } Spring automatically converts the string segment to the parameter type (UUID, Long, int, etc.</description></item><item><title>How Spring Boot Auto-Configuration Works — The Magic Explained</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-auto-configuration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-auto-configuration/</guid><description>Spring Boot &amp;ldquo;just works&amp;rdquo; — you add a dependency and things appear in your application context. This article explains exactly how that happens, so you can debug it when it doesn&amp;rsquo;t, and extend it when you need to.
What Auto-Configuration Actually Does When you add spring-boot-starter-data-jpa to your project, you don&amp;rsquo;t write a single line of config — yet Spring creates a DataSource, EntityManagerFactory, and JpaTransactionManager automatically. That&amp;rsquo;s auto-configuration.
Auto-configuration is a set of @Configuration classes that Spring Boot ships.</description></item><item><title>Integration Testing with @SpringBootTest and Testcontainers</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-integration-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-integration-testing/</guid><description>Integration tests verify that all layers work together — HTTP → controller → service → repository → database. This article shows how to write them efficiently with Testcontainers and manage test isolation.
@SpringBootTest — The Full Context @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class OrderIntegrationTest { // Loads the FULL Spring ApplicationContext // Everything: controllers, services, repositories, security // Starts on a random port (avoids port conflicts when running tests in parallel) } WebEnvironment options Option What it starts Use for RANDOM_PORT Embedded server on random port Full HTTP round-trip tests DEFINED_PORT Embedded server on configured port When you need a fixed port MOCK (default) No real server, MockMvc available Fast tests without real HTTP NONE No server at all Service/repo tests only Testcontainers — Real Database &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Inter-Service Communication with OpenFeign and RestClient</title><link>https://devops-monk.com/tutorials/spring-boot/spring-cloud-feign-restclient/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-cloud-feign-restclient/</guid><description>Services call each other over HTTP. You can use raw RestClient with a URL, or you can use OpenFeign — a declarative HTTP client that turns an interface into a fully functional HTTP client. This article covers both.
OpenFeign: Declarative HTTP Client &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-cloud-starter-openfeign&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; @SpringBootApplication @EnableFeignClients public class OrderServiceApplication { } Defining a Feign Client @FeignClient( name = &amp;#34;inventory-service&amp;#34;, // service name — resolved via Eureka path = &amp;#34;/api/inventory&amp;#34; ) public interface InventoryClient { @GetMapping(&amp;#34;/products/{productId}/availability&amp;#34;) InventoryResponse checkAvailability( @PathVariable UUID productId, @RequestParam int quantity ); @PostMapping(&amp;#34;/reservations&amp;#34;) ReservationResponse reserve(@RequestBody ReservationRequest request); @DeleteMapping(&amp;#34;/reservations/{reservationId}&amp;#34;) void cancelReservation(@PathVariable UUID reservationId); @GetMapping(&amp;#34;/products&amp;#34;) Page&amp;lt;ProductResponse&amp;gt; findProducts( @RequestParam String category, @SpringQueryMap Pageable pageable // Pageable as query params ); } Using it — just inject and call:</description></item><item><title>Introduction to Messaging with Apache Kafka</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-kafka-introduction/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-kafka-introduction/</guid><description>REST APIs are synchronous — the caller waits for a response. Sometimes you don&amp;rsquo;t want that. An order creation shouldn&amp;rsquo;t wait for the inventory system, the notification system, and the analytics system to all respond before confirming to the user. Kafka decouples these concerns.
What Kafka Is Kafka is a distributed event streaming platform. It stores events (messages) in an ordered, immutable log. Producers write to Kafka; consumers read from it.</description></item><item><title>Introduction to Spring Boot — What It Is and Why It Exists</title><link>https://devops-monk.com/tutorials/spring-boot/introduction-to-spring-boot/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/introduction-to-spring-boot/</guid><description>You want to build a Java web application or REST API. You&amp;rsquo;ve heard everyone uses Spring Boot. But what is it exactly, and why does it exist?
This article answers that — clearly — before writing a single line of code.
What Is Spring Boot? Spring Boot is an opinionated framework for building Spring-based Java applications with minimal configuration.
Break that down:
Spring-based — it sits on top of the Spring Framework, which has been the dominant Java application framework since 2003 Minimal configuration — you describe what you want (a web app, a database connection), and Spring Boot figures out how to set it up Opinionated — it makes sensible default choices so you don&amp;rsquo;t have to make every decision yourself The one-line version: Spring Boot lets you go from a blank project to a running application in minutes, with production-ready defaults built in.</description></item><item><title>Introduction to Spring Data JPA</title><link>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-introduction/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-introduction/</guid><description>Most Spring Boot applications need to read and write data to a relational database. Spring Data JPA makes this dramatically simpler by generating the boilerplate data access code for you. This article explains what it is, how it fits together, and how to get started.
The Stack: JPA, Hibernate, and Spring Data JPA These three things work together — understanding the layering matters:
Your Code (Repository interfaces, @Entity classes) │ ▼ Spring Data JPA (generates repository implementations, adds convenience methods) │ ▼ JPA (Jakarta Persistence API) (standard specification: @Entity, @Id, EntityManager, JPQL) │ ▼ Hibernate (JPA implementation) (translates JPA operations to SQL, manages sessions) │ ▼ JDBC (Java Database Connectivity) (sends SQL to the database) │ ▼ PostgreSQL / MySQL / H2 / etc.</description></item><item><title>Introduction: Why Database Versioning Matters</title><link>https://devops-monk.com/tutorials/liquibase/introduction-to-liquibase/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/introduction-to-liquibase/</guid><description>Your application code is in Git. Every change is tracked, reviewed, and reversible. Your database schema is not — it lives in someone&amp;rsquo;s head, a shared wiki page, or a folder of SQL scripts with names like fix_final_v3.sql. This is the problem Liquibase solves.
The Problem: Database Drift On a typical team without database versioning:
Developer A adds a column locally and forgets to tell anyone Developer B&amp;rsquo;s tests fail on a table that exists on A&amp;rsquo;s machine but not on B&amp;rsquo;s Staging has 3 extra columns that production doesn&amp;rsquo;t — or vice versa Nobody knows what the &amp;ldquo;correct&amp;rdquo; schema state is Deploying to production means manually running SQL scripts while hoping nothing was missed This is database drift — the schema diverges between environments and nobody tracks when or why.</description></item><item><title>JPA Entity Mapping: @Entity, @Id, @Column, and More</title><link>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-entity-mapping/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-entity-mapping/</guid><description>JPA entity mapping defines how Java objects translate to database tables. Get it right and your schema is clean, performant, and expressive. This article covers every mapping annotation you&amp;rsquo;ll need.
@Entity and @Table @Entity @Table( name = &amp;#34;orders&amp;#34;, // table name (default: class name) schema = &amp;#34;commerce&amp;#34;, // database schema indexes = { @Index(name = &amp;#34;idx_orders_customer_id&amp;#34;, columnList = &amp;#34;customer_id&amp;#34;), @Index(name = &amp;#34;idx_orders_status_created&amp;#34;, columnList = &amp;#34;status, created_at&amp;#34;) }, uniqueConstraints = { @UniqueConstraint(name = &amp;#34;uq_order_number&amp;#34;, columnNames = &amp;#34;order_number&amp;#34;) } ) public class Order { // .</description></item><item><title>JPA Performance: Solving N+1, Lazy Loading, and Query Optimization</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-jpa-performance-tuning/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-jpa-performance-tuning/</guid><description>JPA makes data access easy — until it silently runs hundreds of queries to load what you think is a single query. This article covers how to find and fix the most common JPA performance problems.
Enable Query Logging First You can&amp;rsquo;t fix what you can&amp;rsquo;t see. Enable SQL logging before optimizing:
logging: level: org.hibernate.SQL: DEBUG org.hibernate.orm.jdbc.bind: TRACE # log bind parameters (Spring Boot 3+) spring: jpa: properties: hibernate: format_sql: true generate_statistics: true # log query count, cache hits, etc.</description></item><item><title>JPQL, @Query, and Native Queries in Spring Data JPA</title><link>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-queries/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-data-jpa-queries/</guid><description>Derived query methods cover simple cases. Complex filtering, aggregation, and reporting queries need JPQL or native SQL. This article covers both with practical examples.
JPQL vs SQL vs Native SQL JPQL Native SQL Writes Entity-oriented (FROM Order o) Table-oriented (FROM orders o) Portability DB-agnostic DB-specific Features Basic SQL + JPA joins Full DB-specific SQL (CTEs, window functions, JSONB) Type-safety Partial None Use for Most queries Reporting, DB-specific features @Query with JPQL public interface OrderRepository extends JpaRepository&amp;lt;Order, UUID&amp;gt; { // Basic JPQL — uses entity/field names, not table/column names @Query(&amp;#34;SELECT o FROM Order o WHERE o.</description></item><item><title>JWT Authentication: Build a Complete Login System</title><link>https://devops-monk.com/tutorials/spring-boot/spring-security-jwt/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-security-jwt/</guid><description>JWT (JSON Web Token) is the standard for stateless REST API authentication. This article builds a complete JWT authentication system — login, token generation, request validation, and token refresh.
What is a JWT? A JWT has three base64url-encoded parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Header (algorithm + type) .eyJzdWIiOiJ1c2VyMTIzIiwicm9sZXMiOlsiVVNFUiJdLCJpYXQiOjE3MTQ3MjY0MDAsImV4cCI6MTcxNDczMDAwMH0 ← Payload .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature The payload contains claims:
{ &amp;#34;sub&amp;#34;: &amp;#34;user123&amp;#34;, // subject (user identifier) &amp;#34;roles&amp;#34;: [&amp;#34;USER&amp;#34;], &amp;#34;iat&amp;#34;: 1714726400, // issued at &amp;#34;exp&amp;#34;: 1714730000 // expires at } The signature is a HMAC of the header+payload — tamper-proof.</description></item><item><title>Logging: SLF4J, Logback, and Structured Logging</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-logging/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-logging/</guid><description>Logging done right gives you everything you need to diagnose production issues. Done wrong, it either buries you in noise or leaves you blind. This article covers the full logging stack — from basics to structured production logging.
The Logging Stack Your Code → SLF4J API → Logback (implementation) → Appenders (console, file, etc.) SLF4J is the facade — your code always calls LoggerFactory.getLogger() and log.info(). The implementation (Logback) is swappable without changing your code.</description></item><item><title>Microservices Architecture: When to Split and When Not to</title><link>https://devops-monk.com/tutorials/spring-boot/microservices-architecture-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/microservices-architecture-guide/</guid><description>Microservices are not a technology — they&amp;rsquo;re an organizational strategy. The right reason to split a monolith is team autonomy and independent deployment, not technical elegance. This article covers when splitting makes sense and how to do it without creating a distributed monolith.
What Microservices Actually Solve Microservices address two problems:
1. Independent deployment: Team A can deploy the Order Service without coordinating with Team B&amp;rsquo;s Payment Service. No shared deployment pipeline, no release freeze, no &amp;ldquo;all-hands&amp;rdquo; deploy windows.</description></item><item><title>Migrating from Spring Boot 3.x to 4.0</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-3-to-4-migration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-3-to-4-migration/</guid><description>Migrating from Spring Boot 3.x to 4.0 is straightforward if you&amp;rsquo;ve kept up with deprecation warnings. This guide walks through every step — from dependency updates to API changes — with before/after examples.
Pre-Migration: Fix Boot 3 Deprecations Before bumping the version, fix all deprecation warnings in your current Boot 3.x project. Every deprecated API in Boot 3 is removed in Boot 4. In IntelliJ IDEA: Analyze → Code Cleanup with &amp;ldquo;Remove deprecated usages&amp;rdquo; enabled.</description></item><item><title>Null Safety with JSpecify</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-jspecify-null-safety/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-jspecify-null-safety/</guid><description>NullPointerException is the most common runtime error in Java. JSpecify is the standardized solution — annotate your APIs and get compile-time null safety. Spring Framework 7 adopts JSpecify throughout, and your code can too.
The Problem // Is this safe? You don&amp;#39;t know without reading the implementation. public Order findById(UUID id) { return repository.findById(id).orElse(null); // returns null! } // The caller has no warning: Order order = service.findById(id); order.cancel(); // ← NullPointerException if not found Without null annotations, every method call is potentially null — you write defensive code everywhere, or you miss a case and get a NPE in production.</description></item><item><title>OAuth2 Authorization Server with Spring Security</title><link>https://devops-monk.com/tutorials/spring-boot/spring-security-oauth2-authorization-server/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-security-oauth2-authorization-server/</guid><description>Most teams use a managed auth provider (Keycloak, Auth0). But sometimes you need your own — multi-tenant SaaS, air-gapped environments, or full control over token contents. Spring Authorization Server provides a production-ready OAuth2 + OIDC implementation.
When to Build Your Own vs Use a Provider Use a managed provider (Keycloak/Auth0): Most applications. Faster to set up, maintained externally, handles compliance.
Build your own: Multi-tenant platforms issuing tokens on behalf of tenant auth providers, air-gapped or regulated environments, products that ARE the identity provider, or when you need full control over token structure and storage.</description></item><item><title>OAuth2 Resource Server: Validate JWTs from an Auth Provider</title><link>https://devops-monk.com/tutorials/spring-boot/spring-security-oauth2-resource-server/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-security-oauth2-resource-server/</guid><description>In production, you rarely build your own auth server. You use an external provider — Keycloak, Auth0, Okta, or AWS Cognito. This article shows how to configure Spring Boot as a Resource Server that validates JWTs issued by any OIDC-compliant provider.
The OAuth2 Architecture ┌─────────────────┐ │ Auth Server │ │ (Keycloak/Auth0) │ │ │ │ Issues JWTs │ │ Publishes JWKS │ └────────┬────────┘ │ ┌────────────┐ │ JWT │ Client │──────────►│ │ (Browser/ │ │ │ Mobile) │ │ └────────────┘ ▼ ┌─────────────────┐ Bearer │ Resource Server │ Token ►│ (Spring Boot) │ │ │ │ Validates JWT │ │ via JWKS URI │ └─────────────────┘ Client authenticates with the Auth Server and receives a JWT Client sends the JWT as Authorization: Bearer &amp;lt;token&amp;gt; to the Resource Server Resource Server validates the JWT by fetching the public key from the Auth Server&amp;rsquo;s JWKS endpoint If valid, the Resource Server processes the request Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Pagination and Sorting in Spring Boot</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-pagination-sorting/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-pagination-sorting/</guid><description>Returning all records from a large table in a single response is a recipe for slow APIs and crashed servers. Pagination is not optional — this article shows how to implement it properly with Spring Data.
The Problem with Returning Everything // Never do this for large datasets @GetMapping(&amp;#34;/api/orders&amp;#34;) public List&amp;lt;Order&amp;gt; getOrders() { return orderRepository.findAll(); // 1 million orders → OutOfMemoryError } Even for &amp;ldquo;small&amp;rdquo; tables, always paginate. Requirements change, data grows.</description></item><item><title>Password Encoding and User Authentication</title><link>https://devops-monk.com/tutorials/spring-boot/spring-security-authentication/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-security-authentication/</guid><description>Every application needs user registration and login. This article builds a complete authentication system — from storing passwords safely to handling failed login attempts.
Never Store Passwords in Plain Text Store a one-way hash, not the password. BCrypt is the industry standard:
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // cost factor 12 → ~250ms per hash on modern hardware // strong enough to slow down brute-force attacks } BCrypt properties:</description></item><item><title>Preconditions: Guard Your Migrations with tableExists, sqlCheck, and More</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-preconditions/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-preconditions/</guid><description>A migration assumes the database is in a specific state. It assumes the table it references exists, the column it modifies is the right type, the user running it has the right privileges, and the database is the right engine. When those assumptions are violated — a hotfix was applied manually, a migration ran out of order, someone ran the wrong changelog against the wrong database — the migration fails in a way that can be hard to diagnose.</description></item><item><title>Producing and Consuming Kafka Messages</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-kafka-producer-consumer/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-kafka-producer-consumer/</guid><description>This article implements Kafka producers and consumers with the full production setup — error handling, retries, dead-letter topics, and idempotent consumers.
Producer: KafkaTemplate @Service @RequiredArgsConstructor @Slf4j public class OrderEventPublisher { private final KafkaTemplate&amp;lt;String, Object&amp;gt; kafkaTemplate; public void publishOrderCreated(Order order) { OrderCreatedEvent event = new OrderCreatedEvent( order.getId(), order.getCustomerId(), order.getCustomerEmail(), order.getItems().stream().map(OrderItemDto::from).toList(), order.getTotalAmount(), Instant.now() ); // Key = customerId: all events for same customer go to same partition kafkaTemplate.send(&amp;#34;order-events&amp;#34;, order.getCustomerId().toString(), event) .whenComplete((result, ex) -&amp;gt; { if (ex !</description></item><item><title>Property Substitution: Environment-Specific Values in Changelogs</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-property-substitution/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-property-substitution/</guid><description>A changeset that hard-codes the schema name ecommerce works in production but breaks when your staging database is called ecommerce_staging. A changeset that seeds a specific admin email works in dev but shouldn&amp;rsquo;t run with the same value in staging. Property substitution lets you parameterize these values so one changelog serves every environment.
How Property Substitution Works Liquibase replaces ${property-name} tokens in your changelog with the value assigned to that property before executing any SQL.</description></item><item><title>Reliable Event Publishing: The Transactional Outbox Pattern</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-kafka-outbox-pattern/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-kafka-outbox-pattern/</guid><description>There is a fundamental problem with publishing Kafka events after a database commit: if the application crashes between the commit and the publish, the event is lost forever. The Transactional Outbox Pattern solves this.
The Problem @Transactional public Order createOrder(CreateOrderRequest request) { Order order = orderRepository.save(buildOrder(request)); // DB committed ✓ kafkaTemplate.send(&amp;#34;order-events&amp;#34;, event); // ← Crash here → event lost, DB already committed // → Inventory never updated, customer never notified return order; } Two distributed systems (PostgreSQL and Kafka) can&amp;rsquo;t be in a single transaction.</description></item><item><title>Resilience Patterns with Resilience4j</title><link>https://devops-monk.com/tutorials/spring-boot/spring-cloud-resilience4j/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-cloud-resilience4j/</guid><description>In microservices, every network call can fail. A slow dependency can exhaust your thread pool, cascading into a full outage. Resilience4j provides the patterns to handle these failures gracefully — without hiding them.
Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;io.github.resilience4j&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;resilience4j-spring-boot3&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;2.2.0&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-aop&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; Circuit Breaker A circuit breaker wraps a remote call. When failures exceed a threshold, the circuit &amp;ldquo;opens&amp;rdquo; and calls fail immediately (without waiting for a timeout) — protecting your thread pool and giving the failing service time to recover.</description></item><item><title>Role-Based Access Control with @PreAuthorize</title><link>https://devops-monk.com/tutorials/spring-boot/spring-security-rbac/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-security-rbac/</guid><description>Roles and permissions control what authenticated users can do. This article implements a complete RBAC system — from URL-level rules to method-level security and resource ownership checks.
Roles vs Permissions Roles are coarse-grained groupings (USER, MANAGER, ADMIN).
Permissions are fine-grained actions (READ_ORDERS, WRITE_PRODUCTS, DELETE_USERS).
Assign permissions to roles:
ADMIN → all permissions MANAGER → READ_ORDERS, WRITE_ORDERS, READ_PRODUCTS, WRITE_PRODUCTS USER → READ_OWN_ORDERS, WRITE_OWN_ORDERS, READ_PRODUCTS Model permissions as a typed enum:
public enum Permission { // Order permissions READ_ORDERS, WRITE_ORDERS, DELETE_ORDERS, READ_OWN_ORDERS, WRITE_OWN_ORDERS, // Product permissions READ_PRODUCTS, WRITE_PRODUCTS, DELETE_PRODUCTS, // User management READ_USERS, WRITE_USERS, DELETE_USERS } public enum Role { USER(Set.</description></item><item><title>Rollback Strategies: Automatic, Custom, Tag-Based, and Count-Based</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-rollback-strategies/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-rollback-strategies/</guid><description>Rollback is the thing you build at 2am when the deployment broke production. The problem is that 2am is exactly the wrong time to discover your rollback blocks are missing, incomplete, or untested.
This article covers rollback from a strategy perspective: how Liquibase generates rollback, when you must write it yourself, how to handle change types that cannot be rolled back at all, and how to validate your rollback strategy in CI so it works when you need it.</description></item><item><title>Service Discovery with Eureka</title><link>https://devops-monk.com/tutorials/spring-boot/spring-cloud-eureka-service-discovery/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-cloud-eureka-service-discovery/</guid><description>In a microservices environment, services scale up and down dynamically. You can&amp;rsquo;t hardcode IP addresses — a service running on 10 pods today has 10 different addresses. Service discovery solves this: services register themselves, and clients look up live instances by name.
What Service Discovery Does Without service discovery: Order Service → http://192.168.1.45:8081/api/inventory ← hardcoded, breaks when IP changes With service discovery: Order Service → &amp;#34;inventory-service&amp;#34; → Eureka → [192.168.1.45:8081, 192.</description></item><item><title>Spring AI: Build a RAG Application</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-spring-ai-rag/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-spring-ai-rag/</guid><description>Large language models know a lot — but not about your data. RAG (Retrieval-Augmented Generation) solves this: find the relevant context from your documents, inject it into the prompt, and let the model answer grounded in your data. This article builds a complete RAG API with Spring AI 2.0.
What You&amp;rsquo;ll Build A Q&amp;amp;A API over your product documentation:
User: &amp;#34;What&amp;#39;s the return policy for electronics?&amp;#34; → Search vector store for relevant docs → Inject matching paragraphs into prompt → Claude/GPT answers based on your actual docs Without RAG: the LLM guesses or hallucinate your policy.</description></item><item><title>Spring Bean Scopes and Lifecycle</title><link>https://devops-monk.com/tutorials/spring-boot/spring-bean-scopes-lifecycle/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-bean-scopes-lifecycle/</guid><description>Every bean in the Spring container has a scope (how many instances exist and for how long) and a lifecycle (what happens when it&amp;rsquo;s created and destroyed). Understanding these prevents subtle bugs and lets you optimize resource usage.
Bean Scopes Overview Scope Instances Available in singleton One per ApplicationContext All apps prototype New instance every time All apps request One per HTTP request Web apps session One per HTTP session Web apps application One per ServletContext Web apps Singleton (Default) By default, every Spring bean is a singleton — one instance per ApplicationContext.</description></item><item><title>Spring Boot Actuator: Health, Metrics, and Management Endpoints</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-actuator/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-actuator/</guid><description>A running application is not enough — you need to know if it&amp;rsquo;s healthy, how it&amp;rsquo;s performing, and what it&amp;rsquo;s doing. Spring Boot Actuator exposes that information through HTTP endpoints and metrics.
Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; By default, only /actuator/health and /actuator/info are exposed over HTTP. Everything else is available via JMX. Enable what you need:
management: endpoints: web: exposure: include: health,info,metrics,prometheus,conditions,beans,env,loggers,threaddump,heapdump base-path: /actuator endpoint: health: show-details: when-authorized # or &amp;#39;always&amp;#39; (dev), &amp;#39;never&amp;#39; (public) show-components: when-authorized metrics: enabled: true server: port: 8081 # expose actuator on a separate port (not public-facing) Health Endpoint GET /actuator/health — used by Kubernetes liveness/readiness probes and load balancers:</description></item><item><title>Spring Boot Integration: Zero-Config Setup and Full Properties Reference</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-spring-boot-integration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-spring-boot-integration/</guid><description>Running Liquibase from the CLI is fine for learning and for standalone scripts. In a real Spring Boot application, you want migrations to run automatically at startup — before the application code touches the database. Spring Boot&amp;rsquo;s Liquibase auto-configuration handles this with zero boilerplate: add the dependency, point to your changelog, and migrations run before the application context is ready.
This article covers the complete integration: Maven/Gradle setup, how auto-run works and why, the full spring.</description></item><item><title>Spring Boot on Kubernetes</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-kubernetes/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-kubernetes/</guid><description>Kubernetes is the standard platform for running containerized microservices. Spring Boot integrates naturally with Kubernetes — Actuator probes map directly to Kubernetes probes, and Spring configuration maps to ConfigMaps and Secrets.
Core Kubernetes Resources Deployment # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: order-service labels: app: order-service spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service version: &amp;#34;1.2.3&amp;#34; spec: containers: - name: order-service image: devopsmonk/order-service:1.2.3 ports: - containerPort: 8080 name: http - containerPort: 8081 name: management # Resource limits — always set these resources: requests: memory: &amp;#34;256Mi&amp;#34; cpu: &amp;#34;250m&amp;#34; limits: memory: &amp;#34;512Mi&amp;#34; cpu: &amp;#34;1000m&amp;#34; # Environment from ConfigMap and Secret envFrom: - configMapRef: name: order-service-config - secretRef: name: order-service-secrets # Individual env vars env: - name: SPRING_PROFILES_ACTIVE value: prod - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.</description></item><item><title>Spring Boot Project Structure Explained</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-project-structure/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-project-structure/</guid><description>A Spring Boot project looks simple on the surface but has a specific structure with specific conventions. Understanding it upfront saves hours of confusion later.
The Standard Layout order-service/ ├── pom.xml ├── mvnw # Maven Wrapper script (Unix) ├── mvnw.cmd # Maven Wrapper script (Windows) ├── .mvn/ │ └── wrapper/ │ └── maven-wrapper.properties └── src/ ├── main/ │ ├── java/ │ │ └── com/devopsmonk/orderservice/ │ │ └── OrderServiceApplication.java │ └── resources/ │ ├── application.</description></item><item><title>Spring Data with PostgreSQL</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-postgresql/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-postgresql/</guid><description>PostgreSQL offers powerful features beyond basic SQL — JSONB, arrays, full-text search, advisory locks. This article shows how to use them from Spring Boot and how to tune HikariCP for production.
Project Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.postgresql&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;postgresql&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; spring: datasource: url: jdbc:postgresql://localhost:5432/orderdb username: app password: ${DB_PASSWORD} driver-class-name: org.postgresql.Driver jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect hibernate: ddl-auto: validate HikariCP — Connection Pool Tuning Spring Boot uses HikariCP by default — the fastest JDBC connection pool.</description></item><item><title>Spring IoC and Dependency Injection</title><link>https://devops-monk.com/tutorials/spring-boot/spring-ioc-dependency-injection/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-ioc-dependency-injection/</guid><description>Every Spring Boot application is built on one idea: the framework creates and wires your objects, not you. This article explains how that works and how to use it effectively.
Inversion of Control In traditional Java, you create objects yourself:
// You control the dependencies public class OrderService { private final OrderRepository repository = new JpaOrderRepository(); private final EmailService email = new SmtpEmailService(&amp;#34;smtp.gmail.com&amp;#34;); } Problems:
OrderService is tightly coupled to specific implementations You can&amp;rsquo;t swap JpaOrderRepository for a mock in tests without editing OrderService If SmtpEmailService needs its own dependencies, you must construct those too Inversion of Control (IoC) flips this: instead of creating your dependencies, you declare what you need and let a container provide them.</description></item><item><title>Spring Security Fundamentals: Filter Chain, Authentication, and Authorization</title><link>https://devops-monk.com/tutorials/spring-boot/spring-security-fundamentals/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-security-fundamentals/</guid><description>Spring Security is powerful but famously hard to understand. This article demystifies the core: the filter chain, how requests are processed, and how authentication and authorization work before writing a line of security config.
Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-security&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; The moment you add this dependency, Spring Boot&amp;rsquo;s auto-configuration secures all endpoints with HTTP Basic authentication. A random password is printed at startup. This is the starting point — you&amp;rsquo;ll replace the defaults.</description></item><item><title>Testing Secured Endpoints</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-security-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-security-testing/</guid><description>Security tests verify that your endpoints behave correctly for different users, roles, and authentication states. This article covers the full toolkit — from simple annotations to custom security contexts.
Setup &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.security&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-security-test&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; spring-boot-starter-test includes this automatically.
@WithMockUser — Simple Role-Based Tests The simplest way to run a test as an authenticated user:
@WebMvcTest(OrderController.class) class OrderControllerSecurityTest { @Autowired MockMvc mockMvc; @MockBean OrderService orderService; // No authentication @Test void unauthenticatedUserIsRejected() throws Exception { mockMvc.</description></item><item><title>Testing Spring Boot Apps: Unit Tests with JUnit 5 and Mockito</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-unit-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-unit-testing/</guid><description>Good tests catch regressions, document behavior, and give you confidence to refactor. Bad tests slow you down. This article covers unit testing at the service layer — fast, focused, no Spring context needed.
Setup &amp;lt;!-- spring-boot-starter-test includes JUnit 5, Mockito, AssertJ --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; This pulls in:
JUnit 5 (Jupiter) — test runner and assertions Mockito — mocking framework AssertJ — fluent assertions (assertThat(...)) Hamcrest — matcher library MockMvc — web layer testing (next article) Testcontainers integration Unit Tests vs Integration Tests Unit Integration Scope One class in isolation Multiple components together Dependencies All mocked Real or near-real Speed Milliseconds Seconds to minutes Context No Spring context Spring context loads Purpose Logic correctness Component interaction Start with unit tests.</description></item><item><title>Testing the Repository Layer with @DataJpaTest</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-repository-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-repository-testing/</guid><description>Repository tests verify your queries work correctly against a real database. Spring Boot&amp;rsquo;s @DataJpaTest starts a minimal slice — only JPA components — making tests fast while still catching real SQL issues.
@DataJpaTest — What It Loads @DataJpaTest is a test slice annotation:
@DataJpaTest class OrderRepositoryTest { // Spring loads: // - Your @Entity classes // - Your @Repository interfaces // - JPA infrastructure (EntityManager, transactions) // - An in-memory H2 database (by default) // // Spring does NOT load: // - @Service, @Controller, @Component classes // - Security configuration // - The full ApplicationContext } Each test method runs in a transaction that&amp;rsquo;s rolled back at the end — no data pollution between tests.</description></item><item><title>Testing the Web Layer with @WebMvcTest and MockMvc</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-web-layer-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-web-layer-testing/</guid><description>Controller tests verify HTTP mapping, request parsing, validation, serialization, and security — without starting a full server. @WebMvcTest + MockMvc gives you a fast, focused web layer test.
@WebMvcTest — What It Loads @WebMvcTest(OrderController.class) class OrderControllerTest { // Spring loads: // - Your @Controller class (and its dependencies) // - DispatcherServlet, MVC configuration // - Jackson ObjectMapper // - Security (if configured) // // Spring does NOT load: // - @Service, @Repository beans // - Database, JPA // // You @MockBean all services } Basic Controller Test @WebMvcTest(OrderController.</description></item><item><title>Transactions: @Transactional, Propagation, and Isolation Levels</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-transactions/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-transactions/</guid><description>A transaction ensures that a group of operations either all succeed or all fail — no partial state. Spring&amp;rsquo;s @Transactional makes this simple to use, but the underlying mechanics matter when things go wrong.
How @Transactional Works @Transactional is implemented via AOP (Aspect-Oriented Programming). Spring wraps your bean in a proxy:
Client calls orderService.create() │ ▼ Spring AOP proxy intercepts the call │ ▼ BEGIN TRANSACTION │ ▼ Your actual method body executes (all DB operations share one connection and transaction) │ ├─ No exception → COMMIT │ └─ RuntimeException thrown → ROLLBACK │ ▼ Client receives result (or exception) This means @Transactional only works when:</description></item><item><title>What's New in Spring Boot 4.0</title><link>https://devops-monk.com/tutorials/spring-boot/spring-boot-4-whats-new/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/spring-boot-4-whats-new/</guid><description>Spring Boot 4.0 (November 2025) is a major release built on Spring Framework 7 and Java 17+. It&amp;rsquo;s the most significant Spring release since Boot 3&amp;rsquo;s Jakarta EE migration. This article covers every change that affects a practicing developer.
Minimum Requirements Spring Boot 3.x Spring Boot 4.0 Java 17 17 (baseline), 21 recommended Spring Framework 6.x 7.x Jakarta EE 10 11 Tomcat 10.x 11.x Hibernate 6.x 7.x Gradle (if used) 7.</description></item><item><title>Your First Migration: MySQL Setup and Running liquibase update</title><link>https://devops-monk.com/tutorials/liquibase/liquibase-first-migration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/liquibase/liquibase-first-migration/</guid><description>You&amp;rsquo;ve read about changesets, tracking tables, and changelog formats. Now you&amp;rsquo;re going to run your first real migration against a live MySQL database. By the end of this article you will have connected Liquibase to MySQL, written a changelog that creates the users table for our e-commerce app, previewed the SQL it generates, applied it, and verified the result in the database.
This is the article where things become real.</description></item><item><title>Your First Spring Boot Application</title><link>https://devops-monk.com/tutorials/spring-boot/first-spring-boot-application/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://devops-monk.com/tutorials/spring-boot/first-spring-boot-application/</guid><description>In this article you&amp;rsquo;ll create a Spring Boot application, add a REST endpoint, and run it — all in under 10 minutes. Then you&amp;rsquo;ll understand exactly what each piece does.
What you&amp;rsquo;ll build: A Spring Boot app that responds to GET /hello with &amp;quot;Hello, Spring Boot!&amp;quot;.
Prerequisites JDK 21 or higher installed (java -version to verify) Maven 3.9+ installed (mvn -version to verify) An IDE — IntelliJ IDEA (recommended), VS Code with Java extension, or Eclipse Step 1: Generate the Project Go to start.</description></item></channel></rss>