<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Devops Monk</title><link>https://blog.devops-monk.com/</link><description>Recent content on Devops Monk</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sat, 23 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.devops-monk.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Container Reuse for Fast Feedback Loops</title><link>https://blog.devops-monk.com/tutorials/testcontainers/container-reuse/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/container-reuse/</guid><description>The singleton pattern shares a container within a single test run. Container reuse goes further — it keeps the container alive after the JVM exits and reuses it in the next test run. The first run pays the startup cost (8 seconds for Kafka). Every subsequent run skips it entirely. For a developer who runs the test suite dozens of times per day, this saves minutes of waiting.
What You&amp;rsquo;ll Learn Enabling container reuse with .</description></item><item><title>Database Migration Testing — Flyway and Liquibase with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/flyway-liquibase-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/flyway-liquibase-testing/</guid><description>Database migrations are the most dangerous code in most applications. A failed migration in production means downtime. A migration that succeeds but corrupts data is worse. Testing migrations against the actual target database — not H2, not a manual inspection — is the only way to catch migration bugs before they reach production.
This article covers testing Flyway and Liquibase migrations with Testcontainers, verifying schema correctness after migration, and testing rollback scenarios.</description></item><item><title>JUnit 5 Integration — Lifecycle and Annotations</title><link>https://blog.devops-monk.com/tutorials/testcontainers/junit5-lifecycle-annotations/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/junit5-lifecycle-annotations/</guid><description>The biggest performance factor in a Testcontainers test suite is how many times you start and stop containers. Start a PostgreSQL container once per JVM and your tests add 2 seconds to total CI time. Start it once per test method and 50 tests cost 100 seconds. Understanding Testcontainers lifecycle management is how you write fast tests without sacrificing isolation.
This article covers all the container lifecycle options in JUnit 5 — from the simplest @Container annotation to the singleton pattern that shares one container across your entire test suite.</description></item><item><title>Kafka Integration Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/kafka-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/kafka-testing/</guid><description>@EmbeddedKafka has long been the standard for Kafka testing in Spring Boot. It works, but it runs a different Kafka implementation than production — one that can accept messages and serialize records in ways that a real Kafka broker would not. KafkaContainer runs the same Confluent Kafka image your production environment uses, with the same broker configuration, same partition behavior, and same consumer group offset management. This article shows how to test Kafka producers, consumers, and dead-letter topics with a real broker.</description></item><item><title>Keycloak and OAuth2 Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/keycloak-oauth2-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/keycloak-oauth2-testing/</guid><description>Testing Spring Security with @WithMockUser and SecurityMockMvcRequestPostProcessors.jwt() verifies that your authorization annotations are wired correctly, but it does not test JWT validation, token signing algorithms, issuer URL verification, or clock skew handling. These require a real OAuth2 provider. KeycloakContainer runs a real Keycloak instance in Docker, giving you a complete OIDC server for integration tests.
What You&amp;rsquo;ll Learn KeycloakContainer setup from the community module Importing a Keycloak realm for tests Retrieving JWT access tokens programmatically Testing protected endpoints with real JWTs Testing role-based access control (RBAC) Testing token expiry and refresh flows Dependencies KeycloakContainer is not in the Testcontainers core library.</description></item><item><title>LocalStack — Testing AWS Services (S3, SQS, SNS) with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/localstack-aws-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/localstack-aws-testing/</guid><description>Testing AWS integrations against real AWS services requires IAM credentials, costs money per test run, and creates real side effects (files in S3, messages in SQS). LocalStack runs a local AWS cloud emulator in Docker — the same API, the same response format, but running locally with no AWS account required. This article covers testing S3, SQS, and SNS integrations with LocalStackContainer.
What You&amp;rsquo;ll Learn LocalStackContainer setup and @ServiceConnection Creating S3 buckets and testing file upload/download Testing SQS message producers and @SqsListener consumers Testing SNS to SQS subscriptions Creating AWS resources before tests with the AWS SDK Testing DynamoDB operations Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;io.</description></item><item><title>MongoDB and NoSQL Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/mongodb-nosql-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/mongodb-nosql-testing/</guid><description>MongoDB&amp;rsquo;s document model introduces testing challenges that relational database tests do not have: schema flexibility (different documents can have different fields), aggregation pipelines, geospatial queries, and text search. Testing these against an embedded Flapdoodle MongoDB works for basic CRUD but breaks down for aggregation pipelines, custom index types, and MongoDB-version-specific behavior. This article covers MongoDB testing with a real MongoDB container.
What You&amp;rsquo;ll Learn MongoDBContainer setup and @ServiceConnection @DataMongoTest slice testing for repositories Testing aggregation pipelines Testing compound indexes and text indexes Querying documents with MongoTemplate Testing document validation rules Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>MonkKit — 106 Free Developer Tools in One Place</title><link>https://blog.devops-monk.com/2026/05/monkkit-developer-tools/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/monkkit-developer-tools/</guid><description>I was tired of bouncing between a dozen different sites just to format JSON or decode a JWT. One tab for a JSON formatter. Another for a Base64 decoder. Another for a DNS lookup. Another for an SPF validator. Browser full of bookmarks, half of them plastered with ads, most requiring an account to do anything useful.
So I built MonkKit — a single place for all of them. 106 free developer tools across 10 categories.</description></item><item><title>MySQL Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/mysql-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/mysql-testing/</guid><description>MySQL is still the most widely deployed relational database in Java applications. Its SQL dialect, strict mode behavior, and character set handling differ from both H2 and PostgreSQL in ways that matter for real applications. This article covers MySQL testing with Testcontainers — from basic setup to MySQL-specific features and the STRICT_TRANS_TABLES mode that catches data truncation bugs H2 silently ignores.
What You&amp;rsquo;ll Learn MySQLContainer setup and configuration @ServiceConnection with MySQL MySQL strict mode and why it matters for tests Character set and collation configuration Testing MySQL-specific SQL functions MariaDB as an alternative Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Parallel Test Execution with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/parallel-test-execution/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/parallel-test-execution/</guid><description>A test suite with 50 integration test classes runs sequentially in 10 minutes. Configured for parallel execution across 4 threads, it completes in 3 minutes. Parallel test execution with Testcontainers requires careful data isolation — tests running simultaneously against the same database will step on each other&amp;rsquo;s data without it. This article covers JUnit 5 parallel configuration, container sharing strategies, and data isolation techniques.
What You&amp;rsquo;ll Learn JUnit 5 parallel execution configuration with junit-platform.</description></item><item><title>PostgreSQL Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/postgresql-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/postgresql-testing/</guid><description>Testing database code against H2 gives you a false sense of security. PostgreSQL has different behavior for ON CONFLICT, RETURNING, JSON operators, full-text search, and dozens of other features. A JPA query that works in H2 dialect may fail against real PostgreSQL. This article shows how to test your Spring Data JPA repositories, custom queries, and database constraints against a real PostgreSQL instance using Testcontainers.
What You&amp;rsquo;ll Learn @ServiceConnection — the Spring Boot 3.</description></item><item><title>RabbitMQ Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/rabbitmq-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/rabbitmq-testing/</guid><description>RabbitMQ&amp;rsquo;s exchange-queue model, routing keys, dead-letter exchanges, and message TTL are features that cannot be tested with mocks. The broker&amp;rsquo;s routing logic — topic exchange patterns, fanout behavior, message acknowledgment, and requeue behavior on rejection — all require a real broker. This article covers RabbitMQ integration testing with RabbitMQContainer.
What You&amp;rsquo;ll Learn RabbitMQContainer setup and @ServiceConnection Configuring exchanges, queues, and bindings for tests Testing @RabbitListener consumers with Awaitility Testing dead-letter exchange and dead-letter queue routing Testing message TTL and automatic expiry Testing acknowledgment modes Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Redis Testing with Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/redis-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/redis-testing/</guid><description>Redis cache testing with a real Redis instance catches behaviors that embedded or mocked alternatives cannot: TTL expiry timing, cache eviction under memory pressure, cluster failover behavior, and the exact serialization format that Redis stores data in. This article covers testing Spring Cache, Spring Session, and distributed locks against a real Redis container.
What You&amp;rsquo;ll Learn Redis GenericContainer setup and @ServiceConnection (Spring Boot 3.2+) Testing Spring Cache with @Cacheable, @CacheEvict, and @CachePut Verifying TTL expiry behavior Testing Spring Session with Redis Testing distributed locks with Redisson or Spring Integration Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Setting Up Testcontainers in a Java Project</title><link>https://blog.devops-monk.com/tutorials/testcontainers/setup-testcontainers-java/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/setup-testcontainers-java/</guid><description>Setting up Testcontainers takes about five minutes. The dependencies are on Maven Central, the BOM manages all version alignment, and Spring Boot 3 has first-class support that eliminates most of the configuration boilerplate. By the end of this article, you will have a Spring Boot project with Testcontainers running, a PostgreSQL container backing your first integration test, and a clear understanding of how to add any additional container module.
What You&amp;rsquo;ll Learn How the Testcontainers BOM works and why you need it Which dependencies to add for Spring Boot, PostgreSQL, and JUnit 5 How to configure Maven and Gradle for Testcontainers tests How to set up Logback so you can see container startup logs How @ServiceConnection eliminates @DynamicPropertySource boilerplate in Spring Boot 3.</description></item><item><title>Singleton Containers and Shared Base Classes</title><link>https://blog.devops-monk.com/tutorials/testcontainers/singleton-containers/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/singleton-containers/</guid><description>With 20 integration test classes, each starting its own PostgreSQL container, you pay 20 container starts at 2 seconds each — 40 seconds of pure overhead before a single assertion runs. The singleton pattern starts one container per JVM, shares it across all test classes, and cuts that 40 seconds to 2. This is the most impactful performance optimization for large Testcontainers test suites.
What You&amp;rsquo;ll Learn The singleton pattern using a static initializer block Abstract base class design for sharing containers and @DynamicPropertySource Why you must not combine @Testcontainers/@Container with the singleton pattern Spring TestContext caching — how it works and how to maximize reuse The @ImportTestcontainers approach for Spring Boot 3.</description></item><item><title>Spring Boot Testing Slices — @DataJpaTest, @WebMvcTest, and @SpringBootTest</title><link>https://blog.devops-monk.com/tutorials/testcontainers/spring-boot-testing-slices/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/spring-boot-testing-slices/</guid><description>@SpringBootTest starts the full application context — all beans, all auto-configurations, all datasources, all message listeners. That is 10–30 seconds of startup time for every test class that needs it. Spring Boot&amp;rsquo;s test slices load a subset of the application context — only the beans relevant to what you are testing. Repository tests that once took 15 seconds with @SpringBootTest take 2 seconds with @DataJpaTest. This article covers all the major test slices and how to combine them with Testcontainers.</description></item><item><title>Testcontainers in CI/CD — GitHub Actions, Docker, and Production Patterns</title><link>https://blog.devops-monk.com/tutorials/testcontainers/testcontainers-cicd/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/testcontainers-cicd/</guid><description>Testcontainers tests that run perfectly on your developer laptop can fail in CI for environmental reasons — Docker daemon availability, image pull timeouts, port conflicts, and resource limits. This final article covers production-ready CI/CD configuration for Testcontainers in GitHub Actions, GitLab CI, and Jenkins, plus Spring Boot 3.1&amp;rsquo;s development-time container support.
What You&amp;rsquo;ll Learn GitHub Actions configuration for Testcontainers (zero extra setup on ubuntu-latest) GitLab CI configuration with Docker-in-Docker Jenkins configuration with Docker socket mounting TestApplication — running your app locally with containers instead of a real database .</description></item><item><title>Understanding Wait Strategies in Testcontainers</title><link>https://blog.devops-monk.com/tutorials/testcontainers/wait-strategies/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/wait-strategies/</guid><description>A container being started does not mean it is ready to accept connections. Docker marks the container as running the moment the entrypoint process starts. PostgreSQL needs another second or two to initialize storage, bind to port 5432, and start accepting connections. If your test code tries to connect before the database is ready, you get Connection refused.
The wrong solution is Thread.sleep(5000). That is a fixed delay: too short on a slow machine, too long on a fast one.</description></item><item><title>Unit Testing vs Integration Testing vs System Testing in Java</title><link>https://blog.devops-monk.com/tutorials/testcontainers/unit-integration-system-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/unit-integration-system-testing/</guid><description>&amp;ldquo;We have 90% test coverage&amp;rdquo; means nothing if 90% of your tests are mocking away the parts that matter. A codebase full of unit tests that mock every dependency can have excellent coverage numbers while completely missing bugs that manifest when real components interact. Understanding the difference between unit tests, integration tests, and system tests — and when to use each — is the foundation of a test suite that actually finds bugs.</description></item><item><title>What Is Testcontainers and Why You Need It</title><link>https://blog.devops-monk.com/tutorials/testcontainers/what-is-testcontainers/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/what-is-testcontainers/</guid><description>Your CI pipeline goes green. You deploy to staging. The application crashes on startup because Flyway migrations fail against MySQL — but all your tests used H2. Your team has seen this before. Tests that pass on every machine, fail in production. Not because the business logic was wrong, but because the test infrastructure was fake.
This is the problem Testcontainers was built to solve.
This article covers what Testcontainers is, why traditional approaches to integration testing fall short, and how Testcontainers gives you test environments that match production — automatically, repeatably, with no shared test database to maintain.</description></item><item><title>WireMock — Testing External REST API Integrations</title><link>https://blog.devops-monk.com/tutorials/testcontainers/wiremock-testing/</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/testcontainers/wiremock-testing/</guid><description>Most Spring Boot applications call external APIs — payment gateways, shipping providers, notification services, CRM systems. Testing these integrations is hard because external APIs have rate limits, authentication requirements, and a habit of being unavailable at exactly the wrong moment. WireMock runs a real HTTP server in a Docker container, accepts your requests, and returns whatever responses you configure. This article covers WireMock as a Testcontainer for testing Spring Boot REST client integrations.</description></item><item><title>Claude Code Plugins: The Complete Guide to Building and Sharing Extensions</title><link>https://blog.devops-monk.com/2026/05/claude-code-plugins-guide/</link><pubDate>Sun, 17 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/claude-code-plugins-guide/</guid><description>Skills, agents, and hooks you add to .claude/ are powerful — but they are locked to one project. Every time you start a new repo you copy the same files, maintain them in multiple places, and drift out of sync. Claude Code plugins solve this: a plugin is a shareable, versioned package that carries all your customisations and can be installed in any project with one command.
This post covers what plugins are, when to use them, and how to build a real one from scratch — a DevOps helper that ships a deployment skill, a pre-deploy safety hook, and an MCP server connection to your Kubernetes cluster.</description></item><item><title>AOT Compilation in Java 25 (JEP 514 &amp; 515): Faster Startup, Zero Warm-Up</title><link>https://blog.devops-monk.com/tutorials/java25/aot-compilation/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/aot-compilation/</guid><description>The Java Startup Problem Java&amp;rsquo;s performance story has always had one weak spot: startup time.
When a JVM starts, it:
Loads and verifies class bytecode Interprets bytecode (slow) Profiles which methods are called most (warm-up) Compiles hot methods to native code via JIT (takes time and CPU) Eventually reaches peak throughput This process takes seconds for large applications. For a Spring Boot application, typical warm-up to peak throughput can take 10–30 seconds.</description></item><item><title>Compact Object Headers (JEP 519): 33% Less Heap Overhead</title><link>https://blog.devops-monk.com/tutorials/java25/compact-object-headers/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/compact-object-headers/</guid><description>What Is an Object Header? Every single Java object — every String, every Integer, every record, every array — carries a header that the JVM uses for bookkeeping. Your code never sees this header; it lives alongside the object&amp;rsquo;s fields in memory.
Before Java 25, on a 64-bit JVM, the header occupied 96 to 128 bits (12–16 bytes):
┌─────────────────────────────────────────────────────────┐ │ Mark Word (64 bits) │ │ ─ identity hash code │ │ ─ lock state (biased lock / thin lock / fat lock) │ │ ─ GC age bits │ ├─────────────────────────────────────────────────────────┤ │ Class Pointer (32 bits compressed / 64 bits full) │ │ ─ pointer to the object&amp;#39;s class (Klass* in HotSpot) │ └─────────────────────────────────────────────────────────┘ With UseCompressedOops (default), the class pointer is compressed to 32 bits, giving a 96-bit (12-byte) header.</description></item><item><title>Compact Source Files &amp; Instance Main Methods (JEP 512): Java as a Scripting Language</title><link>https://blog.devops-monk.com/tutorials/java25/compact-source-files/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/compact-source-files/</guid><description>The Ceremony Problem When a student writes their first Java program, they copy this boilerplate:
public class HelloWorld { public static void main(String[] args) { System.out.println(&amp;#34;Hello, World!&amp;#34;); } } Three layers of ceremony for one line of logic:
A public class whose name must match the filename A public static void main(String[] args) signature with specific keywords The actual code, buried inside two levels of braces This has been the #1 on-ramp friction point in Java for 30 years.</description></item><item><title>Flexible Constructor Bodies (JEP 513): Validate Before super()</title><link>https://blog.devops-monk.com/tutorials/java25/flexible-constructor-bodies/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/flexible-constructor-bodies/</guid><description>The Old Rule That Caused So Much Pain From Java 1.0 through Java 24, a constructor that extended another class had one rigid rule:
super(...) or this(...) must be the first statement in the constructor body.
This was enforced by the compiler. Not because of a deep technical reason — but because the JVM specification had always required that the superclass be fully initialized before the subclass could do anything with the object.</description></item><item><title>Generational Shenandoah (JEP 516): Best GC for Low Latency</title><link>https://blog.devops-monk.com/tutorials/java25/generational-shenandoah/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/generational-shenandoah/</guid><description>GC Background: Why Generations Matter The Generational Hypothesis is the foundation of most modern GC design:
Most objects die young.
In a typical Java application, the vast majority of objects are short-lived: request/response objects, DTOs, builder instances, stream pipeline intermediates. They are created, used briefly, and then immediately eligible for collection.
A GC that knows about this pattern can be far more efficient than one that treats all objects equally:</description></item><item><title>Java 25 Security: Key Derivation Function API &amp; PEM Encodings</title><link>https://blog.devops-monk.com/tutorials/java25/security-kdf-pem/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/security-kdf-pem/</guid><description>Overview Java 25 ships two important security additions:
JEP 510 — Key Derivation Function (KDF) API — Final. A standard API for HKDF, PBKDF2, and other KDFs. JEP 470 — PEM Encodings of Cryptographic Objects — Preview. Read and write .pem files without third-party libraries. These fill two long-standing gaps: Java had the underlying crypto but no clean standard API for key derivation and PEM I/O.
Part 1: Key Derivation Function API (JEP 510) What Is Key Derivation?</description></item><item><title>Java 25: The LTS That Graduates Four Years of Previews</title><link>https://blog.devops-monk.com/tutorials/java25/java25-overview/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/java25-overview/</guid><description>Why Java 25 Matters Java 25 was released on September 16, 2025 — exactly two years after Java 21, following the cadence Oracle committed to when it moved to a two-year LTS cycle.
But Java 25 is not just &amp;ldquo;Java 21 with more stuff&amp;rdquo;. It is the release where four years of preview work finally becomes production API. Features that Java developers have been testing since Java 21 — Scoped Values, Flexible Constructor Bodies, Module Import Declarations, Compact Source Files — all graduate to final status.</description></item><item><title>Module Import Declarations (JEP 511): One Import to Rule Them All</title><link>https://blog.devops-monk.com/tutorials/java25/module-import-declarations/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/module-import-declarations/</guid><description>The Problem: Import Hell Every Java developer has experienced this. You open a file and before you see a single line of business logic, you wade through a wall of imports:
import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.</description></item><item><title>Primitive Types in Patterns (JEP 507): Pattern Match Every Type</title><link>https://blog.devops-monk.com/tutorials/java25/primitive-patterns/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/primitive-patterns/</guid><description>Note: JEP 507 is a preview feature in Java 25 (3rd preview). Enable it with --enable-preview at compile and runtime. It is expected to finalize in Java 26 or 27.
The Asymmetry in Pattern Matching Java 21 gave us pattern matching for switch and instanceof. It is a great feature — but it had a glaring asymmetry: it only worked with reference types.
Object obj = 42; // Java 21: works fine if (obj instanceof Integer i) { System.</description></item><item><title>Scoped Values (JEP 506): The Final ThreadLocal Replacement</title><link>https://blog.devops-monk.com/tutorials/java25/scoped-values/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/scoped-values/</guid><description>The ThreadLocal Problem ThreadLocal has been the standard way to pass contextual data through a call chain without polluting method signatures since Java 1.2. Every Java developer has seen it used for things like:
Web request context (user ID, correlation ID, tenant ID) Database transaction binding Security principal propagation Logging MDC (Mapped Diagnostic Context) Here is the classic pattern:
public class RequestContext { // ThreadLocal stores one value per thread private static final ThreadLocal&amp;lt;String&amp;gt; CURRENT_USER = new ThreadLocal&amp;lt;&amp;gt;(); public static void set(String userId) { CURRENT_USER.</description></item><item><title>Setting Up Java 25: Install, Tooling &amp; IDE Configuration</title><link>https://blog.devops-monk.com/tutorials/java25/java25-setup/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/java25-setup/</guid><description>Installing Java 25 Option 1: SDKMAN! (Recommended) SDKMAN! is the easiest way to install and switch between Java versions on macOS and Linux.
# Install SDKMAN! if you don&amp;#39;t have it curl -s &amp;#34;https://get.sdkman.io&amp;#34; | bash source &amp;#34;$HOME/.sdkman/bin/sdkman-init.sh&amp;#34; # List available Java 25 distributions sdk list java | grep &amp;#34;25&amp;#34; # Install Eclipse Temurin 25 (open-source, most common) sdk install java 25-tem # Set as default globally sdk default java 25-tem # Verify java -version # openjdk version &amp;#34;25&amp;#34; 2025-09-16 Switch between versions per project:</description></item><item><title>Structured Concurrency (JEP 505): Preview 5 — What Changed?</title><link>https://blog.devops-monk.com/tutorials/java25/structured-concurrency/</link><pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java25/structured-concurrency/</guid><description>Note: JEP 505 is a preview feature in Java 25 (5th preview). Enable with --enable-preview. The API has been stable for several rounds and is expected to finalize in Java 26.
The Problem with Unstructured Concurrency Classic Java concurrency with ExecutorService is unstructured: you submit tasks, and those tasks have no formal relationship with the code that submitted them. This causes three recurring problems:
Problem 1: Partial failure leaves orphaned tasks ExecutorService exec = Executors.</description></item><item><title>Building a Full-Stack Personalised Gifts E-Commerce Platform with Next.js 15</title><link>https://blog.devops-monk.com/2026/05/personalised-gifts-ecommerce-platform/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/personalised-gifts-ecommerce-platform/</guid><description>Building an e-commerce site is one of those projects that sounds simple until you actually start. Payments, authentication, inventory, personalisation, image handling, email notifications, admin tools, live chat — each piece is its own rabbit hole. I built PersonalisedGifts, a full UK-market personalised gifts store from scratch, wiring all of these together into a single cohesive product.
The site lets customers browse gifts by category (Mugs, Jewellery, Canvas Prints, Home Décor) or occasion (Birthday, Wedding, Christmas, New Baby), personalise their chosen item with custom text, images, fonts, or colours, and pay via Stripe (cards, Apple Pay, Google Pay, Klarna) or directly from their bank account via Tink&amp;rsquo;s open banking integration.</description></item><item><title>@MappedSuperclass: Sharing Fields Without Inheritance Tables</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/mapped-superclass/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/mapped-superclass/</guid><description>Introduction Almost every entity in a real application shares a few common fields: id, createdAt, updatedAt, and perhaps version. Writing these in every entity class is repetitive and error-prone. @MappedSuperclass lets you define them once in a base class that all entities extend — without creating a parent table or any inheritance mapping in the database.
What Is @MappedSuperclass? @MappedSuperclass marks a class whose field mappings are inherited by subclass entities.</description></item><item><title>@SendTo and @KafkaHandler: Chaining Consumers and Multi-Type Dispatch</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/sendto-kafkahandler/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/sendto-kafkahandler/</guid><description>@SendTo — Chaining Listeners @SendTo on a @KafkaListener method automatically sends the return value to another Kafka topic. This is how you build event pipelines without manually calling KafkaTemplate.send() in your listener.
flowchart LR T1["orders\n(OrderPlacedEvent)"] T2["orders-confirmed\n(OrderConfirmedEvent)"] T3["inventory-events\n(StockReservedEvent)"] T1 -->|"@KafkaListener\n@SendTo"| L1["confirmOrder()"] L1 --> T2 T2 -->|"@KafkaListener\n@SendTo"| L2["reserveStock()"] L2 --> T3 Basic @SendTo @KafkaListener(topics = &amp;#34;orders&amp;#34;, groupId = &amp;#34;confirmation-service&amp;#34;) @SendTo(&amp;#34;orders-confirmed&amp;#34;) public OrderConfirmedEvent onOrder(OrderPlacedEvent event) { // Return value is automatically sent to &amp;#34;orders-confirmed&amp;#34; return new OrderConfirmedEvent( event.</description></item><item><title>@Transactional in Depth: Propagation, Isolation, and Rollback</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/transactional-propagation-isolation/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/transactional-propagation-isolation/</guid><description>What @Transactional Does @Transactional tells Spring to wrap the annotated method in a database transaction. Spring opens the transaction before the method starts and commits (or rolls back) when it ends. The annotation works via an AOP proxy — understanding this proxy model is essential for avoiding the most common @Transactional bugs.
The AOP Proxy Model Spring creates a proxy around your bean. When code outside the bean calls a @Transactional method, the call goes through the proxy, which manages the transaction:</description></item><item><title>Actuator Security and Production Hardening</title><link>https://blog.devops-monk.com/tutorials/spring-security/actuator-security-production/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/actuator-security-production/</guid><description>The Actuator Security Problem Spring Boot Actuator exposes endpoints that reveal sensitive information about your application — environment variables, configuration properties, heap dumps, thread dumps, and the ability to shut down the application remotely.
An exposed /actuator/env endpoint can leak database passwords, API keys, and JWT signing secrets. An exposed /actuator/shutdown is a denial-of-service button. Actuator security is not optional in production.
Actuator Endpoints and Their Risk Level Endpoint Exposes Risk /actuator/health Application health Low — often public /actuator/info App metadata Low /actuator/metrics JVM/HTTP metrics Medium — business data /actuator/env All configuration properties (including secrets) Critical /actuator/configprops All @ConfigurationProperties values Critical /actuator/loggers Log levels (writable) High /actuator/heapdump Full JVM heap as a file Critical /actuator/threaddump Thread state Medium /actuator/mappings All URL mappings Medium — reveals API surface /actuator/shutdown Kills the JVM Critical /actuator/auditevents Security events High Step 1: Expose Only What You Need By default, only health is exposed over HTTP.</description></item><item><title>Advanced Streams: flatMap, Collectors, Grouping, and Partitioning</title><link>https://blog.devops-monk.com/tutorials/java8/streams-advanced/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/streams-advanced/</guid><description>flatMap in Depth The Collectors API covers perhaps 90% of real-world aggregation needs, but the moment you need something beyond grouping, partitioning, or joining, the custom Collector is there. Understanding flatMap and the full Collectors toolkit — including when to write your own — separates competent Java 8 developers from those who still reach for a for loop whenever a problem gets slightly unusual.
flatMap is the most powerful transformation in the Streams API.</description></item><item><title>Auditing: @CreatedDate, @LastModifiedBy, and Hibernate Envers</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/auditing/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/auditing/</guid><description>Why Auditing Matters Production systems must answer: &amp;ldquo;Who changed this? When? What did it look like before?&amp;rdquo; Without auditing infrastructure, you add created_at, updated_at, created_by, updated_by columns manually to every entity — dozens of lines of boilerplate repeated everywhere.
Spring Data JPA auditing fills these fields automatically. Hibernate Envers goes further: it captures every revision of every entity in separate audit tables.
Spring Data JPA Auditing Enable Auditing @Configuration @EnableJpaAuditing(auditorAwareRef = &amp;#34;auditorProvider&amp;#34;) public class JpaAuditingConfig { @Bean public AuditorAware&amp;lt;String&amp;gt; auditorProvider() { return () -&amp;gt; Optional.</description></item><item><title>AuthenticationManager, AuthenticationProvider, and UserDetailsService</title><link>https://blog.devops-monk.com/tutorials/spring-security/authentication-manager-provider/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/authentication-manager-provider/</guid><description>The Authentication Delegation Chain Spring Security separates requesting authentication from performing authentication. A filter receives a username and password — but it does not check them itself. It passes the request to AuthenticationManager, which delegates to one or more AuthenticationProvider instances:
flowchart LR F[Authentication Filter\nForm Login / Basic / JWT] -->|UsernamePasswordToken| AM[AuthenticationManager\nProviderManager] AM -->|try each provider| AP1[DaoAuthenticationProvider\nfor DB users] AM -->|try each provider| AP2[LdapAuthenticationProvider\nfor LDAP users] AM -->|try each provider| AP3[Custom Provider\nfor API key] AP1 -->|if supported| UDS[UserDetailsService\nloadUserByUsername] UDS --> DB[(Database)] AP1 -->|verify password| PE[PasswordEncoder] AP1 -->|success| Token[Authenticated Token] Token --> F This chain is the core of Spring Security&amp;rsquo;s extensibility — you can plug in any number of authentication mechanisms without touching the filter or the rest of the framework.</description></item><item><title>Avro Serialization with Confluent Schema Registry</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/avro-schema-registry/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/avro-schema-registry/</guid><description>Why Avro and Schema Registry? JSON has no schema enforcement — a producer can change a field name and silently break every consumer. Avro + Schema Registry solves this:
Avro gives you a compact binary format with a schema definition Schema Registry stores and versions schemas, enforces compatibility rules, and prevents breaking changes from reaching consumers flowchart LR subgraph Producer["Order Service"] E["OrderPlacedEvent"] -->|"KafkaAvroSerializer"| SR["Schema Registry\n(register/lookup schema)"] SR --> Bytes["[schema_id (4 bytes)] + [avro payload]"</description></item><item><title>Basic Entity Mapping: @Entity, @Table, @Id, @Column</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/basic-entity-mapping/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/basic-entity-mapping/</guid><description>Introduction Entity mapping is the process of telling Hibernate how your Java class corresponds to a database table, and how each field maps to a column. JPA provides a rich set of annotations for this — from the minimal @Entity and @Id to the detailed @Column with constraints.
This article covers every annotation and attribute you need to map entities precisely and confidently.
@Entity @Entity marks a class as a JPA entity — a Java object that maps to a database table.</description></item><item><title>Best Practices Reference: The Complete Spring Batch Checklist</title><link>https://blog.devops-monk.com/tutorials/spring-batch/spring-batch-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/spring-batch-best-practices/</guid><description>Introduction This is the final article in the Spring Batch Tutorial series. It consolidates every hard-won lesson into actionable checklists — use it as a review before deploying a new batch job to production, or as a reference when debugging an existing one.
Part 1: Job Design Always design for restartability Every step that writes to a database uses idempotent SQL (ON DUPLICATE KEY UPDATE or WHERE NOT EXISTS) Every ItemReader has a unique .</description></item><item><title>Cascade Types and Orphan Removal: Managing Lifecycle Propagation</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/cascade-types-orphan-removal/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/cascade-types-orphan-removal/</guid><description>Introduction Cascade types control which persistence operations (save, merge, delete, refresh) propagate from a parent entity to its associated children. orphanRemoval controls what happens when a child is removed from the parent&amp;rsquo;s collection. Getting cascade wrong is one of the most common causes of unexpected deletes and data integrity issues in JPA applications.
The Six Cascade Types cascade = CascadeType.PERSIST // propagate persist (save new entity) cascade = CascadeType.MERGE // propagate merge (re-attach detached entity) cascade = CascadeType.</description></item><item><title>Collection Factory Methods (JEP 269): Immutable List, Set, and Map</title><link>https://blog.devops-monk.com/tutorials/java11/collection-factory-methods/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/collection-factory-methods/</guid><description>Why Collection Factory Methods? Before Java 9, creating a small immutable collection was tedious:
// Java 8 — three lines, two classes, mutable intermediate List&amp;lt;String&amp;gt; names = Collections.unmodifiableList( Arrays.asList(&amp;#34;Alice&amp;#34;, &amp;#34;Bob&amp;#34;, &amp;#34;Charlie&amp;#34;) ); // Java 8 — even worse for Map Map&amp;lt;String, Integer&amp;gt; scores = new HashMap&amp;lt;&amp;gt;(); scores.put(&amp;#34;Alice&amp;#34;, 90); scores.put(&amp;#34;Bob&amp;#34;, 85); Map&amp;lt;String, Integer&amp;gt; immutableScores = Collections.unmodifiableMap(scores); JEP 269 (Java 9) introduced static factory methods that create truly immutable collections with a single expression:</description></item><item><title>Collections and Map Enhancements: forEach, merge, compute, replaceAll</title><link>https://blog.devops-monk.com/tutorials/java8/collections-maps/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/collections-maps/</guid><description>Overview Java 8 didn&amp;rsquo;t just add Streams — it also enhanced the existing Iterable, Collection, List, and Map interfaces with default methods that cover the most common imperative patterns. Understanding these methods lets you write cleaner code even without streams.
The three methods worth memorising immediately are computeIfAbsent (the most useful new Map method), merge (for accumulation patterns), and removeIf (eliminating the iterator-based removal pattern). Together they eliminate the most common sources of verbose boilerplate in collection-heavy code.</description></item><item><title>CompletableFuture: Async Pipelines and Non-Blocking Composition</title><link>https://blog.devops-monk.com/tutorials/java8/completablefuture/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/completablefuture/</guid><description>The Problem with Future&amp;lt;T&amp;gt; Fetching a product page on an e-commerce site requires at least three data sources: product details, live pricing, and inventory availability. Done sequentially, a 200 ms call to each service adds up to 600 ms. Done in parallel with a properly composed CompletableFuture pipeline, the wall-clock time drops to roughly the slowest of the three — around 200 ms. This article shows exactly how to build that pipeline and everything else you need to write robust async code in Java 8.</description></item><item><title>Consumer @Bean Configuration: ConcurrentKafkaListenerContainerFactory</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/consumer-bean-configuration/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/consumer-bean-configuration/</guid><description>Why @Bean Configuration? application.properties covers the common cases. But real applications need multiple listener factories — one for orders with manual acknowledgment and concurrency 3, another for analytics events with batch listening and different deserializers. @Bean configuration gives you a factory per use case, full IDE support, and the ability to wire in custom components like error handlers and interceptors.
The Factory Relationship flowchart TD CF["ConsumerFactory\n(connection + deserialization config)"] LCF["ConcurrentKafkaListenerContainerFactory\n(container behaviour config)"</description></item><item><title>Consumer Groups, Offsets, and the __consumer_offsets Topic</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/consumer-groups-offsets/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/consumer-groups-offsets/</guid><description>What Is a Consumer Group? A consumer group is a set of consumer instances that jointly consume a topic. Kafka assigns each partition to exactly one consumer within the group at a time. This is what enables parallel processing: multiple consumers in the same group read different partitions simultaneously.
flowchart LR subgraph Topic["Topic: orders — 4 partitions"] P0["Partition 0"] P1["Partition 1"] P2["Partition 2"] P3["Partition 3"] end subgraph CG["Consumer Group: inventory-service"] C1["</description></item><item><title>Consumer Groups: Parallel Processing and Partition Assignment Strategies</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/consumer-groups/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/consumer-groups/</guid><description>Consumer Group Fundamentals A consumer group is how Kafka distributes work across multiple consumers. Each partition in a topic is assigned to exactly one consumer instance in the group at any given time.
flowchart TB subgraph Topic["Topic: orders — 6 partitions"] P0["P0"] P1["P1"] P2["P2"] P3["P3"] P4["P4"] P5["P5"] end subgraph CG1["Group: inventory-service (3 instances)"] I1["Instance 1\nP0, P1"] I2["Instance 2\nP2, P3"] I3["Instance 3\nP4, P5"] end subgraph CG2["Group: notification-service (1 instance)"] N1["Instance 1\nP0,P1,P2,P3,P4,P5"] end P0 &amp; P1 --> I1 P2 &amp; P3 --> I2 P4 &amp; P5 --> I3 P0 &amp; P1 &amp; P2 &amp; P3 &amp; P4 &amp; P5 --> N1 Both groups receive all events — they are independent.</description></item><item><title>Context-Specific Deserialization Filters (JEP 415): Securing Java Deserialization</title><link>https://blog.devops-monk.com/tutorials/java17/deserialization-filters/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/deserialization-filters/</guid><description>Finalized in Java 17 (JEP 415). Extends JEP 290 (Java 9), which introduced the basic deserialization filter API.
Why Deserialization Is Dangerous Java object deserialization (ObjectInputStream.readObject()) is one of the most exploited attack surfaces in Java. When a Java application deserializes untrusted bytes, the JVM instantiates arbitrary classes and calls their methods as a side effect — before your application code even sees the result.
Attackers craft gadget chains: sequences of serializable classes in common libraries (Apache Commons Collections, Spring Framework, etc.</description></item><item><title>CORS: Cross-Origin Requests and Preflight Configuration</title><link>https://blog.devops-monk.com/tutorials/spring-security/cors-configuration/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/cors-configuration/</guid><description>What Is CORS? The Same-Origin Policy (SOP) is a browser security rule: JavaScript on app.example.com cannot read responses from api.example.com — different subdomain, different origin. Without this, any website could use a visitor&amp;rsquo;s authenticated session to silently call other sites on their behalf.
CORS (Cross-Origin Resource Sharing) is the mechanism by which a server explicitly relaxes SOP for trusted origins. When a browser sees a cross-origin request, it checks whether the server has granted permission before allowing JavaScript to read the response.</description></item><item><title>CSRF Protection: How It Works and When to Disable It</title><link>https://blog.devops-monk.com/tutorials/spring-security/csrf-protection/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/csrf-protection/</guid><description>What Is a CSRF Attack? Cross-Site Request Forgery (CSRF) tricks an authenticated user&amp;rsquo;s browser into making an unintended request to your application.
The attack:
Alice is logged into bank.com — her browser holds a valid session cookie Alice visits evil.com evil.com contains &amp;lt;img src=&amp;quot;https://bank.com/transfer?to=attacker&amp;amp;amount=5000&amp;quot;&amp;gt; Alice&amp;rsquo;s browser fires the request, automatically attaching her bank.com session cookie bank.com receives an authenticated request that Alice never intended to make The attack works because browsers automatically send cookies with cross-origin requests.</description></item><item><title>Custom Queries with @Query: JPQL and Native SQL</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/custom-queries-jpql-native-sql/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/custom-queries-jpql-native-sql/</guid><description>Why @Query Exists Derived query methods (Article 17) are great for simple conditions, but they break down fast. A method like findByOrderStatusAndCustomerCityAndPriceBetweenOrderByCreatedAtDesc is unreadable and still cannot express a JOIN or aggregation.
@Query lets you write the exact JPQL or SQL you need, attached directly to a repository method — keeping your repository clean while giving you full query control.
JPQL vs Native SQL JPQL Native SQL Language Entity/field names Table/column names Database portable Yes No JOIN FETCH Yes No Window functions No Yes Full-text search No Yes Complex analytics Limited Full power Start with JPQL.</description></item><item><title>Custom Serializers and Deserializers</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/custom-serializers/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/custom-serializers/</guid><description>When to Write a Custom Serializer Spring Kafka ships JSON and Avro support. You need a custom serializer when:
Your team uses Protobuf or MessagePack and wants native support You need a compact binary format for high-throughput topics (pricing ticks, sensor readings) You&amp;rsquo;re integrating with a legacy system that publishes a fixed binary protocol You want deterministic serialization for event deduplication or content-addressed storage The Serializer and Deserializer Interfaces // org.</description></item><item><title>Custom Type Conversions: AttributeConverter and Enums</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/custom-type-conversions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/custom-type-conversions/</guid><description>Introduction JPA handles most Java types automatically — String, Integer, LocalDate, BigDecimal. But what about a Java enum? A List&amp;lt;String&amp;gt; stored as JSON? A custom Money type? JPA&amp;rsquo;s AttributeConverter and @Enumerated let you control exactly how any Java type maps to a database column.
Mapping Enums with @Enumerated Java enums are common in domain models. JPA maps them with @Enumerated:
public enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED } public enum ProductStatus { ACTIVE, INACTIVE, DISCONTINUED } @Column(length = 20, nullable = false) @Enumerated(EnumType.</description></item><item><title>Date and Time API (JSR-310): LocalDate, ZonedDateTime, Duration, Period</title><link>https://blog.devops-monk.com/tutorials/java8/date-time-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/date-time-api/</guid><description>Why java.util.Date Had to Go If you have ever tracked down a bug caused by a SimpleDateFormat shared across threads, or spent an afternoon understanding why a Calendar.get(Calendar.MONTH) returns 0 for January, you will appreciate why JSR-310 was one of the most eagerly anticipated Java 8 features. The new java.time API is not a patch on the old API — it is a complete redesign that makes an entire class of date and time bugs impossible by construction.</description></item><item><title>Dead Letter Topics: Routing Failed Messages with DeadLetterPublishingRecoverer</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/dead-letter-topics/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/dead-letter-topics/</guid><description>What Is a Dead Letter Topic? When a record fails processing and retries are exhausted, you have two options: skip it (losing the data) or park it somewhere for inspection and reprocessing. A dead-letter topic (DLT) is that parking lot — a Kafka topic that holds records that could not be processed, enriched with error metadata headers.
flowchart LR subgraph Main["orders topic"] R1["record: offset 42\n(bad data)"] end subgraph DLT["orders.DLT topic"] R2["</description></item><item><title>Default and Static Interface Methods: Backwards-Compatible API Evolution</title><link>https://blog.devops-monk.com/tutorials/java8/default-static-methods/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/default-static-methods/</guid><description>The Problem: Evolving Interfaces Without Breaking Implementations Default methods solved a problem that had blocked the Java Collections team from improving the core APIs for years: how do you add methods to an interface without breaking every existing implementation? The answer Java 8 gave — concrete implementations on the interface itself — has wider implications than just Collection.stream(). It is the mechanism behind the fluent Comparator API, the Predicate composition methods, and the design pattern of composable interface contracts.</description></item><item><title>Derived Query Methods: Finding Data Without Writing SQL</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/derived-query-methods/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/derived-query-methods/</guid><description>Introduction Spring Data JPA can generate JPQL queries directly from method names. You declare a method like findByStatusAndPriceGreaterThan, and Spring Data parses the name, derives the query, and generates the implementation. No SQL, no @Query, no boilerplate.
How Derived Query Parsing Works Spring Data JPA splits method names into a subject and a predicate:
findBy Email And Status ↑ ↑ ↑ ↑ subject property operator property The subject determines the operation type (find, count, exists, delete).</description></item><item><title>Dirty Checking, Flush Modes, and the First-Level Cache</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/dirty-checking-flush-modes/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/dirty-checking-flush-modes/</guid><description>The Persistence Context Revisited Article 3 introduced the persistence context — the in-memory cache of managed entities. This article goes deeper: how does the persistence context detect changes? When does it flush them to the database? And how can you tune this behaviour?
Dirty Checking When you modify a managed entity, you don&amp;rsquo;t call save() or update(). You simply change the field:
@Transactional public void giveDiscount(Long productId, BigDecimal discount) { Product product = productRepository.</description></item><item><title>Domain Object Security: Access Control Lists (ACLs)</title><link>https://blog.devops-monk.com/tutorials/spring-security/domain-object-acl/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/domain-object-acl/</guid><description>What ACLs Solve Role-based access control answers: &amp;ldquo;Can this user perform this action?&amp;rdquo; ACLs answer: &amp;ldquo;Can this user perform this action on this specific object?&amp;rdquo;
Consider a document management system:
Alice owns Document #42 — she can read and edit it Bob is a reviewer on Document #42 — he can read it but not edit Carol has no permission on Document #42 — she gets a 403 This cannot be expressed with roles alone.</description></item><item><title>Dynamic Listener Containers and Programmatic Topic Registration</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/dynamic-listeners/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/dynamic-listeners/</guid><description>Why Dynamic Listeners? @KafkaListener is declared at compile time. Some scenarios require listeners created at runtime:
Multi-tenant SaaS — each tenant onboards to their own topic; you can&amp;rsquo;t redeploy to add @KafkaListener for each new tenant Feature flags — enable or disable a listener without a deployment Plugin systems — modules register their own topic subscriptions when loaded Admin APIs — operators subscribe to new topics via a REST endpoint ConcurrentMessageListenerContainer The core building block is ConcurrentMessageListenerContainer — the same class @KafkaListener uses internally, but constructed and started manually:</description></item><item><title>Embedded Types and Value Objects: @Embeddable and @Embedded</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/embedded-types-value-objects/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/embedded-types-value-objects/</guid><description>Introduction Not every concept in your domain deserves its own table. An Address — street, city, state, postal code — is a value object: it has no identity of its own, it belongs to an entity. JPA&amp;rsquo;s @Embeddable lets you model this as a separate Java class while storing it in the owning entity&amp;rsquo;s table.
What Is an Embeddable? An @Embeddable class is a Java class whose fields are mapped to columns in the owning entity&amp;rsquo;s table — not a separate table.</description></item><item><title>Enhanced Pseudo-Random Number Generators (JEP 356): A Modern PRNG API</title><link>https://blog.devops-monk.com/tutorials/java17/enhanced-prng/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/enhanced-prng/</guid><description>Finalized in Java 17 (JEP 356).
The Problem with Java&amp;rsquo;s Old PRNG API Java&amp;rsquo;s random number generation before Java 17 had several problems:
1. java.util.Random has poor statistical properties. The default PRNG algorithm is a 48-bit linear congruential generator (LCG). It fails many statistical tests (spectral test, birthday test). Its 2^48 period is short — exhaustible in seconds on modern hardware.
2. No common interface. java.util.Random, java.util.concurrent.ThreadLocalRandom, java.util.SplittableRandom, and java.</description></item><item><title>Entity Graphs and Batch Loading: Precision Fetching</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/entity-graphs-batch-loading/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/entity-graphs-batch-loading/</guid><description>The Problem @EntityGraph Solves JOIN FETCH in @Query solves N+1 but creates inflexibility: the fetch plan is baked into the query. Two different use cases — an order detail page (needs items + customer) and an order list page (needs only customer) — require two different queries with different JOIN FETCHes.
@EntityGraph separates the fetch plan from the query. You define what to load at the call site, and Spring Data JPA generates the appropriate JOIN.</description></item><item><title>Entity Lifecycle States: Transient, Managed, Detached, Removed</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/entity-lifecycle-states/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/entity-lifecycle-states/</guid><description>Introduction Every JPA entity is always in one of four states. The state determines whether Hibernate is tracking the entity, whether changes are detected automatically, and what operations are valid. Understanding these states explains a large class of JPA bugs — especially the dreaded LazyInitializationException and detached entity errors.
The Four States new Customer() │ │ persist / save() ▼ TRANSIENT ──────────────────────► MANAGED (not tracked) (tracked by persistence context) │ │ evict / clear / close ▼ DETACHED (no longer tracked) │ │ merge() ▼ MANAGED (re-attached) MANAGED ─── remove / delete() ──► REMOVED (delete scheduled, still in context) 1.</description></item><item><title>Error Handling Basics: DefaultErrorHandler and CommonErrorHandler</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/error-handling-basics/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/error-handling-basics/</guid><description>What Happens When a Listener Throws? Without an error handler, an uncaught exception from @KafkaListener causes the container to log the error and retry the same record on the next poll — indefinitely. One bad record can block an entire partition forever.
DefaultErrorHandler fixes this: it retries a configurable number of times with backoff, then calls a ConsumerRecordRecoverer (e.g. send to a dead-letter topic) and moves on.
DefaultErrorHandler — The Modern API Spring Kafka 2.</description></item><item><title>Fetch Types: EAGER vs LAZY Loading</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/fetch-types-eager-vs-lazy/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/fetch-types-eager-vs-lazy/</guid><description>Introduction Every relationship in JPA has a fetch type: either EAGER (load immediately with the parent) or LAZY (load only when accessed). The defaults are counterintuitive, and getting fetch types wrong is one of the top causes of performance problems and LazyInitializationException in JPA applications.
Default Fetch Types Annotation Default fetch type Performance risk @ManyToOne EAGER Loads related entity on every query — can be expensive @OneToOne EAGER Same — loads profile/address on every customer load @OneToMany LAZY Correct default — collections are only loaded when needed @ManyToMany LAZY Correct default The defaults for @ManyToOne and @OneToOne are EAGER — a poor choice that the JPA spec made for historical reasons.</description></item><item><title>Files and IO Enhancements (Java 11)</title><link>https://blog.devops-monk.com/tutorials/java11/files-io-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/files-io-api/</guid><description>The Problem: File IO Boilerplate Reading a file&amp;rsquo;s content in Java 8 required several lines of boilerplate, even for simple tasks:
// Java 8 — read entire file as String String content; try (var reader = new BufferedReader(new FileReader(&amp;#34;config.txt&amp;#34;))) { content = reader.lines().collect(Collectors.joining(&amp;#34;\n&amp;#34;)); } // Java 8 — write a String to a file try (var writer = new BufferedWriter(new FileWriter(&amp;#34;output.txt&amp;#34;))) { writer.write(content); } Java 11 reduced this to one-liners.</description></item><item><title>Filtering Messages with RecordFilterStrategy</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/message-filtering/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/message-filtering/</guid><description>Why Filter at the Container Level? Multiple consumers can share a topic. The inventory service only cares about PLACED orders; the analytics service wants everything. Rather than putting if (event.getStatus() != PLACED) return; at the top of every listener, Spring Kafka lets you filter records before they reach your method — keeping business logic clean and making the filter reusable across listeners.
How RecordFilterStrategy Works flowchart LR Broker -->|"poll() → [r1, r2, r3, r4]"</description></item><item><title>Flight Recorder and JVM Monitoring (JEP 328)</title><link>https://blog.devops-monk.com/tutorials/java11/flight-recorder/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/flight-recorder/</guid><description>What Is Java Flight Recorder? Java Flight Recorder (JFR) is a low-overhead, always-on profiling and diagnostics framework built into the JVM. It was a commercial feature of Oracle JDK until JEP 328 (Java 11) open-sourced it as part of OpenJDK.
JFR collects data about JVM internals and application behaviour — method profiling, allocation, GC pauses, thread states, I/O, lock contention — with a typical overhead of 1–2% in production.
This makes it fundamentally different from traditional profilers (JProfiler, YourKit): those profilers cause 10–50% overhead, making them impractical for production.</description></item><item><title>Foreign Function &amp; Memory API (JEP 412): First Look at Project Panama</title><link>https://blog.devops-monk.com/tutorials/java17/foreign-function-memory/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/foreign-function-memory/</guid><description>Incubator Feature in Java 17 (JEP 412 — First Incubator). Package: jdk.incubator.foreign. Requires --add-modules jdk.incubator.foreign at compile and runtime. This API evolved significantly across Java 18 (JEP 419), 19 (JEP 424), 20 (JEP 434), and was finalized in Java 22 (JEP 454). See the Java 21 article series for the final API. This article covers the Java 17 incubator version and the Project Panama vision.
Why Replace JNI? Java Native Interface (JNI) has been the way to call native code from Java since Java 1.</description></item><item><title>Foreign Function &amp; Memory API (JEP 442): Calling Native Code Without JNI</title><link>https://blog.devops-monk.com/tutorials/java21/foreign-function-memory/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/foreign-function-memory/</guid><description>Preview Feature in Java 21 — Finalized in Java 22 (JEP 454). The API shown here is the Java 21 preview version; it is nearly identical to the final API.
Why Replace JNI? Java Native Interface (JNI) has been the standard way to call native code since Java 1.1. It works but is notoriously painful:
Requires writing C wrapper code for every native call Native method signatures must exactly match Java declarations (or you get silent crashes) Memory management is manual — leak native memory once and you have a slow memory leak JNI calls disable JIT optimizations around the call site Debugging native crashes through JNI is extremely difficult The Foreign Function &amp;amp; Memory (FFM) API replaces all of this: call native functions directly from Java, manage native memory safely with automatic lifetime management, and do it without writing a single line of C.</description></item><item><title>Form Login Authentication: From Request to Session</title><link>https://blog.devops-monk.com/tutorials/spring-security/form-login/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/form-login/</guid><description>Form Login: The Classic Web Authentication Flow Form login is the traditional authentication mechanism for server-rendered web applications. The user fills in a username and password form, the server verifies credentials, creates a session, and returns a session cookie. All subsequent requests carry that cookie.
sequenceDiagram participant Browser participant SSF as Spring Security Filters participant AM as AuthenticationManager participant SS as HTTP Session Store participant Controller Browser->>SSF: GET /dashboard (unauthenticated) SSF->>SS: Save original request (RequestCache) SSF-->>Browser: 302 Redirect to /login Browser->>SSF: GET /login SSF-->>Browser: 200 HTML login page Browser->>SSF: POST /login {username, password} SSF->>AM: authenticate(UsernamePasswordToken) AM-->>SSF: Authentication (success) SSF->>SS: Store SecurityContext in session SSF-->>Browser: 302 Redirect to /dashboard\nSet-Cookie: JSESSIONID=abc123 Browser->>SSF: GET /dashboard (JSESSIONID cookie) SSF->>SS: Load SecurityContext from session SSF->>Controller: Request with authenticated user Controller-->>Browser: 200 Dashboard HTML Minimal Configuration @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .</description></item><item><title>Functional Interfaces: Predicate, Function, Supplier, Consumer, and More</title><link>https://blog.devops-monk.com/tutorials/java8/functional-interfaces/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/functional-interfaces/</guid><description>What Is a Functional Interface? Functional interfaces are the type system bridge that makes lambda expressions work in Java 8. Without them, the compiler would have no way to know what type to assign to a lambda. Once you understand how the 43 built-in interfaces in java.util.function are organised — and when to write your own — you will find that the same patterns (compose, chain, validate, transform) appear everywhere in a Java 8 codebase.</description></item><item><title>Garbage Collection: G1GC, ZGC, Epsilon, and AppCDS</title><link>https://blog.devops-monk.com/tutorials/java11/garbage-collection/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/garbage-collection/</guid><description>GC Changes Across Java 9–11 Release Change JEP Java 9 G1GC becomes the default GC JEP 248 Java 9 Unified GC logging (-Xlog:gc*) JEP 271 Java 10 Parallel Full GC for G1 JEP 307 Java 10 Application Class-Data Sharing (AppCDS) JEP 310 Java 11 Epsilon: No-Op GC JEP 318 Java 11 ZGC: Scalable Low-Latency GC (experimental) JEP 333 G1GC as Default (JEP 248, Java 9) G1 (Garbage-First) replaced Parallel GC as the default on systems with ≥2 CPUs and ≥2 GB heap.</description></item><item><title>Generational ZGC (JEP 439): Sub-Millisecond Pauses at Any Scale</title><link>https://blog.devops-monk.com/tutorials/java21/generational-zgc/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/generational-zgc/</guid><description>Why GC Still Matters in 2024 Even with better APIs and faster hardware, garbage collection pauses remain one of the top causes of latency spikes in Java services. A 200ms GC pause in a payment processing service is a customer-visible failure. A 50ms pause in a trading system is a missed execution window.
Java 21 delivers Generational ZGC — the most capable GC Java has ever shipped — as a production-ready, non-preview feature.</description></item><item><title>Handling Deserialization Errors Gracefully</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/deserialization-errors/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/deserialization-errors/</guid><description>The Problem: Poison Pills at Deserialization Time A malformed byte sequence — truncated JSON, wrong Avro schema, corrupt payload — throws an exception during deserialization, before the listener method is called. Without special handling, this record blocks the partition indefinitely: the consumer fetches it, fails to deserialize, and fetches it again on the next poll.
sequenceDiagram participant Broker participant Container participant Deserializer loop forever without ErrorHandlingDeserializer Container->>Broker: poll() Broker-->>Container: [good-record, CORRUPT-RECORD, good-record] Container->>Deserializer: deserialize(CORRUPT-RECORD) Deserializer-->>Container: JsonProcessingException 💥 Note over Container: Partition blocked — same record on every poll end ErrorHandlingDeserializer solves this by catching the deserialization exception and wrapping it in a DeserializationException that the listener container can route to the error handler.</description></item><item><title>How Spring Security Works: The Big Picture</title><link>https://blog.devops-monk.com/tutorials/spring-security/how-spring-security-works/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/how-spring-security-works/</guid><description>Why Spring Security Is Hard to Learn Spring Security is not complex because the concepts are complex. Authentication and authorization are straightforward ideas. Spring Security is hard because it hides its machinery: a request comes in, something happens, an endpoint is secured or not — and without knowing the internal architecture, the behaviour feels magical and the configuration feels arbitrary.
This article removes the magic. By the end, you will have the mental model needed to understand every other concept in this series.</description></item><item><title>HTTP Authorization: Securing Endpoints with requestMatchers</title><link>https://blog.devops-monk.com/tutorials/spring-security/http-authorization/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/http-authorization/</guid><description>HTTP Authorization: The Last Line of Defense AuthorizationFilter is the last filter in the Spring Security chain. After all authentication filters have run (and either set an Authentication or left it as anonymous), AuthorizationFilter makes the final access decision: allow or deny.
The rules you write in authorizeHttpRequests() are evaluated in order, first match wins.
flowchart TD Request[HTTP Request] --> Auth[AuthorizationFilter] Auth --> Rules{Evaluate rules\nin order} Rules -->|Rule 1 matches| Decision1{Allowed?</description></item><item><title>HTTP Basic Authentication and Stateless APIs</title><link>https://blog.devops-monk.com/tutorials/spring-security/http-basic-authentication/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/http-basic-authentication/</guid><description>What Is HTTP Basic Authentication? HTTP Basic is the simplest authentication scheme defined in the HTTP specification (RFC 7617). The client encodes username:password in Base64 and sends it in the Authorization header with every request:
Authorization: Basic YWxpY2U6c2VjcmV0 ↑ Base64(&amp;#34;alice:secret&amp;#34;) Important: Base64 is encoding, not encryption. Anyone who intercepts the request can decode the credentials instantly. HTTP Basic must only be used over HTTPS.
sequenceDiagram participant Client as API Client participant SSF as Spring Security Filters participant AM as AuthenticationManager Client->>SSF: GET /api/data\n(no Authorization header) SSF-->>Client: 401 Unauthorized\nWWW-Authenticate: Basic realm="</description></item><item><title>HTTP Client API (JEP 321): HTTP/2, Async, and Authentication</title><link>https://blog.devops-monk.com/tutorials/java11/http-client-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/http-client-api/</guid><description>Why a New HTTP Client? HttpURLConnection — Java&amp;rsquo;s HTTP API since Java 1.1 — has deep design problems:
Mutable shared state makes it error-prone in multithreaded code No built-in HTTP/2 support No built-in async; non-blocking requires manual thread management Clunky API: setDoOutput(true), getOutputStream(), connect() in sequence No support for reactive streams JEP 321 (Java 11) standardised the HTTP Client API that was incubating since Java 9. The new API lives in java.</description></item><item><title>Idempotent Producers: Eliminating Duplicate Messages</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/idempotent-producer/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/idempotent-producer/</guid><description>The Duplicate Problem With acks=all and retries enabled, a produce request might be acknowledged by the broker, but the acknowledgment is lost in the network before reaching the producer. The producer, seeing no response, retries — sending the same record again. The broker writes it a second time. The consumer sees a duplicate.
sequenceDiagram participant Producer participant Leader as Broker (Leader) Producer->>Leader: ProduceRequest: OrderPlaced (orderId=1001) Leader->>Leader: Write record at offset 42 ✓ Leader--xProducer: ProduceResponse LOST (network failure) Note over Producer: No ack received — retrying Producer->>Leader: ProduceRequest: OrderPlaced (orderId=1001) [RETRY] Leader->>Leader: Write record at offset 43 ✓ (DUPLICATE!</description></item><item><title>Inheritance Strategies: SINGLE_TABLE, JOINED, TABLE_PER_CLASS</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/inheritance-strategies/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/inheritance-strategies/</guid><description>Introduction Object-oriented code uses inheritance to share behaviour. Relational databases have no concept of inheritance. JPA bridges this gap with three strategies for mapping a class hierarchy to tables. Understanding when each is appropriate prevents schema headaches and performance problems.
The Domain Example An e-commerce system has different types of discount:
Discount (abstract) ├── PercentageDiscount (e.g., 10% off) └── FixedAmountDiscount (e.g., $5 off) All discounts share: id, name, validFrom, validUntil. PercentageDiscount adds: percentage (e.</description></item><item><title>Introduction to JPA, Hibernate, and Spring Data JPA</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/introduction-jpa-hibernate-spring-data/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/introduction-jpa-hibernate-spring-data/</guid><description>Introduction Every Spring Boot application that touches a relational database eventually encounters three terms used almost interchangeably: JPA, Hibernate, and Spring Data JPA. They are related but distinct, and understanding the difference is essential before writing a single line of mapping code.
This article explains what each one is, how they fit together, and why this stack is the dominant approach to Java database access.
The Problem: Object-Relational Impedance Mismatch Java applications work with objects: classes, inheritance, collections, references.</description></item><item><title>Java 11 Overview: The Road from Java 8 Through Java 9, 10, to LTS</title><link>https://blog.devops-monk.com/tutorials/java11/java11-overview/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/java11-overview/</guid><description>Why Java 11 Matters Java 8 was released in March 2014. It dominated enterprise Java for nearly a decade, but it misses a decade&amp;rsquo;s worth of language improvements, API modernisation, JVM advances, and security hardening. Java 11 (September 2018) is the first Long-Term Support release after Java 8, and it packages three releases of evolution into a single supported baseline.
For most teams the question is not whether to upgrade, but how.</description></item><item><title>Java 11 Production Checklist and Performance Best Practices</title><link>https://blog.devops-monk.com/tutorials/java11/production-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/production-best-practices/</guid><description>Production Readiness Checklist [ ] JDK distribution chosen and version pinned [ ] Heap and Metaspace sized correctly [ ] GC selected and tuned for your workload [ ] Container-aware JVM flags set [ ] AppCDS archive built for faster startup [ ] JFR always-on recording configured [ ] GC logging enabled with rotation [ ] Security-related algorithms locked down [ ] Thread and connection pool sizes verified [ ] JVM exit flags prevent silent crashes Baseline JVM Flags for Java 11 Start with these flags and tune from here:</description></item><item><title>Java 17 Production Checklist and Performance Best Practices</title><link>https://blog.devops-monk.com/tutorials/java17/production-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/production-best-practices/</guid><description>Production Baseline JVM Flags Start every Java 17 production deployment with this baseline:
java \ # GC — choose one (see GC section) -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ \ # Heap sizing -Xms4g -Xmx4g \ \ # GC logging — essential for diagnosis -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=5,filesize=20m \ \ # OOM diagnostics -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/log/app/heap-dump.hprof \ -XX:+ExitOnOutOfMemoryError \ \ # Metaspace -XX:MaxMetaspaceSize=512m \ \ # Code cache -XX:ReservedCodeCacheSize=512m \ \ # JFR — always-on profiling -XX:StartFlightRecording=duration=0,filename=/var/log/app/profile.</description></item><item><title>Java 17: The LTS That Delivers — What Changed from Java 11</title><link>https://blog.devops-monk.com/tutorials/java17/java17-overview/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/java17-overview/</guid><description>Java 17: The Landmark LTS Java 17 was released on September 14, 2021 as a Long-Term Support (LTS) release. It is the successor to Java 11 (released September 2018) as the recommended production baseline for enterprise and cloud Java deployments.
Between Java 11 and Java 17, six non-LTS releases (Java 12 through 16) delivered a continuous stream of language improvements on a six-month cadence. Java 17 is where five of the most important new features — Records, Sealed Classes, Pattern Matching for instanceof, Text Blocks, and Switch Expressions — all reached their final, production-ready status simultaneously.</description></item><item><title>Java 21 Production Checklist and Performance Best Practices</title><link>https://blog.devops-monk.com/tutorials/java21/production-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/production-best-practices/</guid><description>The Production Mindset Migrating to Java 21 unlocks new capabilities, but production readiness requires deliberate configuration. The JVM defaults are conservative — designed to work reasonably across a wide range of workloads, not to be optimal for any specific one.
This article covers:
Which JVM flags to set for every production Java 21 deployment GC selection and tuning for different workload profiles Virtual thread configuration and monitoring Container-aware JVM settings Observability and profiling Startup and memory optimization JVM Flags: The Production Baseline Start every Java 21 production deployment with this baseline flag set:</description></item><item><title>Java 21: The LTS Release That Changes Everything</title><link>https://blog.devops-monk.com/tutorials/java21/java21-overview/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/java21-overview/</guid><description>Why Java 21 Is Different Every third Java release is an LTS — Long-Term Support. Java 21 is the fourth LTS after Java 8, 11, and 17. But unlike previous LTS releases, which were largely incremental, Java 21 delivers features that fundamentally change how you write concurrent code, how the JVM manages memory and GC, and how Java competes with dynamic languages for expressiveness.
Three years of Project Loom work lands as Virtual Threads — production-ready, requiring zero framework changes for most Spring Boot or Jakarta EE applications.</description></item><item><title>Java 8 Best Practices and Patterns for Production Code</title><link>https://blog.devops-monk.com/tutorials/java8/java8-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/java8-best-practices/</guid><description>Introduction Java 8 introduced a fundamentally different programming model. The features — lambdas, streams, Optional, CompletableFuture — interact with each other in ways that produce clean, readable code when used correctly and confusing, brittle code when used incorrectly. This article consolidates the most important production-ready guidance from across the series into a single reference, along with the migration checklist for moving a Java 7 codebase to Java 8.
Streams Best Practices Do: Use streams for transformations and aggregations // Good: filter → transform → collect List&amp;lt;String&amp;gt; premiumNames = customers.</description></item><item><title>Java 8 Overview: The Release That Changed Everything</title><link>https://blog.devops-monk.com/tutorials/java8/java8-overview/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/java8-overview/</guid><description>Before We Start — Feel the Difference You&amp;rsquo;re about to learn Java 8. Before diving into history and theory, let&amp;rsquo;s just feel what changed. Here&amp;rsquo;s the same task written in Java 7 and Java 8:
Task: Find all names that start with &amp;ldquo;A&amp;rdquo;, uppercase them, sort them, and collect into a list.
// Java 7 — 10 lines, two passes, one temporary variable, zero joy List&amp;lt;String&amp;gt; result = new ArrayList&amp;lt;&amp;gt;(); for (String name : names) { if (name.</description></item><item><title>JDK Encapsulation and Removed APIs (JEP 403, 306, 407, 411): What It Means for Your Code</title><link>https://blog.devops-monk.com/tutorials/java17/jdk-encapsulation-removed-apis/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/jdk-encapsulation-removed-apis/</guid><description>Overview Java 17 includes several platform changes that can break existing code during migration. This article covers four JEPs that affect compatibility:
JEP Change Impact 403 Strongly Encapsulate JDK Internals HIGH — breaks libraries using internal APIs 407 Remove RMI Activation LOW — niche feature, deprecated since Java 15 411 Deprecate Security Manager MEDIUM — some applications use SecurityManager 306 Restore Always-Strict Floating-Point LOW — affects numeric edge cases JEP 403: Strongly Encapsulate JDK Internals What Changed In Java 8 and earlier, code could access any JDK internal API via reflection — setAccessible(true) bypassed access controls.</description></item><item><title>JSON Serialization: JsonSerializer, JsonDeserializer, and Type Mapping</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/json-serialization/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/json-serialization/</guid><description>The Serialization Problem Kafka stores bytes. KafkaTemplate&amp;lt;String, OrderPlacedEvent&amp;gt; needs to turn your Java object into bytes for the producer, and @KafkaListener needs to turn those bytes back into the right Java class on the consumer. Spring Kafka ships JsonSerializer and JsonDeserializer built on Jackson to handle this — but they have several sharp edges that break in real multi-service deployments.
How Spring Kafka JSON Serialization Works flowchart LR subgraph Producer["Order Service"</description></item><item><title>JVM Improvements: Metaspace, PermGen Removal, and Performance</title><link>https://blog.devops-monk.com/tutorials/java8/jvm-improvements/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/jvm-improvements/</guid><description>PermGen Removal: The End of a Classic Error OutOfMemoryError: PermGen space was the Java error that launched a thousand Stack Overflow questions. Application servers would run fine for hours and then fall over during a hot redeploy. The fix — adding more -XX:MaxPermSize — was a band-aid. Java 8 removed the underlying problem entirely.
Before Java 8, the JVM heap was divided into several regions. One of them — Permanent Generation (PermGen) — held class metadata, interned strings, and bytecode.</description></item><item><title>JWT Authentication: Stateless Token-Based Security</title><link>https://blog.devops-monk.com/tutorials/spring-security/jwt-authentication/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/jwt-authentication/</guid><description>What Is JWT? JWT (JSON Web Token, RFC 7519) is a compact, URL-safe token format for representing claims between two parties. It is the standard for stateless REST API authentication — the server issues a signed token at login, and the client presents it on every subsequent request. No session, no cookie, no server-side state.
JWT Structure A JWT has three Base64URL-encoded parts separated by dots:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsInJvbGVzIjpbIlJPTEVfVVNFUiJdfQ.signature Header Payload Signature block-beta columns 3 A["</description></item><item><title>Kafka Architecture: Brokers, Topics, Partitions, and Replicas</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-architecture/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-architecture/</guid><description>The Cluster: Brokers and the Controller A Kafka cluster is a group of servers, each called a broker. Brokers store data and serve producer/consumer requests. One broker in the cluster acts as the controller — it manages partition leadership, handles broker joins and departures, and coordinates rebalancing.
In KRaft mode (Kafka 3.3+, the default from Kafka 4.0), the controller is built into Kafka itself — no ZooKeeper needed.
flowchart TB subgraph Cluster["</description></item><item><title>Kafka CLI: Creating Topics, Producing, and Consuming Messages</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-cli/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-cli/</guid><description>Why Learn the CLI First? Before writing any Spring code, understanding the Kafka CLI tools gives you the ability to:
Verify your cluster is working correctly Inspect topics and partitions Debug consumer lag issues Replay messages from specific offsets Reset consumer groups during incident recovery All CLI tools are in Kafka&amp;rsquo;s bin/ directory. In Docker, run them with docker exec:
docker exec kafka &amp;lt;tool&amp;gt; &amp;lt;args&amp;gt; kafka-topics.sh: Managing Topics Create a Topic docker exec kafka kafka-topics.</description></item><item><title>Kafka Consumer in Spring Boot: @KafkaListener Basics</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-consumer-basics/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-consumer-basics/</guid><description>How @KafkaListener Works @KafkaListener is a Spring Kafka annotation that registers a method as a Kafka consumer. Under the hood, Spring Kafka creates a ConcurrentMessageListenerContainer — a managed thread pool that continuously polls the broker and dispatches records to your method.
flowchart LR Broker["Kafka Broker"] subgraph Container["ConcurrentMessageListenerContainer"] T1["Poll Thread 1\n(Partition 0)"] T2["Poll Thread 2\n(Partition 1)"] T3["Poll Thread 3\n(Partition 2)"] end Method["@KafkaListener\nvoid onOrderPlaced(...)"] Broker -->|"fetch records"| T1 Broker -->|"fetch records"| T2 Broker -->|"</description></item><item><title>Kafka Producer in Spring Boot: KafkaTemplate Basics</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-producer-basics/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-producer-basics/</guid><description>How a Spring Kafka Producer Works KafkaTemplate is the central Spring Kafka class for sending messages. It wraps the native Kafka KafkaProducer, manages serialization, and provides a Spring-friendly API for sending records.
flowchart LR App["Your Service\n(OrderService)"] KT["KafkaTemplate\n(Spring Kafka)"] Buffer["Producer Buffer\n(RecordAccumulator)"] Sender["Sender Thread\n(NetworkClient)"] Broker["Kafka Broker\n(Leader Partition)"] App -->|"send(topic, key, value)"| KT KT -->|serialize + route| Buffer Buffer -->|batch when full\nor linger.ms elapsed| Sender Sender -->|ProduceRequest| Broker Broker -->|ProduceResponse| Sender Sender -->|callback| App The send is asynchronous by default — KafkaTemplate.</description></item><item><title>Kafka Streams with Spring Boot: Stateless and Stateful Processing</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-streams/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-streams/</guid><description>Kafka Streams vs @KafkaListener @KafkaListener is a consumer — it reads records and processes them one by one or in batches. Kafka Streams is a stream processing library — it builds a topology of transformations that runs continuously, with built-in state stores, windowed aggregations, and join operations.
Aspect @KafkaListener Kafka Streams Processing model Consume and process Topology of operators Stateful processing Manual (external DB) Built-in state stores (RocksDB) Windowed aggregations Manual Native (time, session, hopping) Joins Manual KStream-KTable, KStream-KStream Fault tolerance Committed offsets Changelog topics + offsets Use when Imperative event handling Stream transformations and aggregations Maven Dependency &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Kafka Transactions and Exactly-Once Semantics</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/transactions-eos/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/transactions-eos/</guid><description>Why Transactions? At-least-once delivery means a record can be processed and produced more than once after a crash. For most applications, idempotent consumers handle this. But when you need a hard guarantee — either the produce happens and the offset commits, or neither does — you need Kafka transactions.
Common scenarios:
Consume → transform → produce (read from one topic, write to another) where partial completion is unacceptable Exactly-once aggregations in financial or billing systems Atomic multi-topic produce where records to multiple topics must all land or none land How Kafka Transactions Work sequenceDiagram participant Producer participant Broker participant Consumer Producer->>Broker: initTransactions() [registers transactional.</description></item><item><title>KafkaAdmin and AdminClient: Managing Topics Programmatically</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-admin/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-admin/</guid><description>Why Manage Topics Programmatically? CLI commands work for one-time setup. Production services need:
Startup validation — verify required topics exist before the application starts Auto-provisioning — create topics at deployment time with correct config Dynamic tenant onboarding — create per-tenant topics at runtime Config drift detection — compare actual topic config against expected values Spring Kafka provides KafkaAdmin for declarative topic management and AdminClient for imperative operations.
KafkaAdmin — Declarative Topic Creation Declare NewTopic beans — Spring Kafka creates them at startup if they don&amp;rsquo;t exist:</description></item><item><title>Key Encapsulation Mechanism API (JEP 452): Post-Quantum Cryptography in Java</title><link>https://blog.devops-monk.com/tutorials/java21/kem-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/kem-api/</guid><description>Why Post-Quantum Cryptography Now? Classical public-key cryptography (RSA, ECDH) relies on mathematical problems that are hard for classical computers — factoring large integers or solving the discrete logarithm problem. A sufficiently powerful quantum computer running Shor&amp;rsquo;s algorithm could solve these problems efficiently, breaking all existing RSA and ECC-based security.
Quantum computers capable of breaking 2048-bit RSA don&amp;rsquo;t exist yet. But &amp;ldquo;harvest now, decrypt later&amp;rdquo; attacks are real: adversaries intercept and store encrypted traffic today, planning to decrypt it once quantum computers mature.</description></item><item><title>KRaft Mode: Running Kafka Without ZooKeeper</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kraft-mode/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kraft-mode/</guid><description>Why ZooKeeper Had to Go For the first decade of Kafka&amp;rsquo;s existence, every Kafka cluster required an Apache ZooKeeper cluster to manage metadata: controller election, topic configurations, partition leadership, access control lists, and consumer group state.
This created real problems:
flowchart TB subgraph OldArchitecture["Old Architecture: Kafka + ZooKeeper"] subgraph ZK["ZooKeeper Cluster (3+ nodes)"] Z1["ZK Node 1"] Z2["ZK Node 2"] Z3["ZK Node 3"] end subgraph Kafka["Kafka Cluster"] K1["Broker 1\n(Controller)"] K2["Broker 2"] K3["</description></item><item><title>Lambda Expressions (JEP 126): Syntax, Closures, and Target Typing</title><link>https://blog.devops-monk.com/tutorials/java8/lambdas/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/lambdas/</guid><description>The Problem Lambdas Solve Every Java 7 developer has written the same five lines of boilerplate to sort a list or run a background task. Lambda expressions eliminate that ceremony entirely — and once you understand target typing, closures, and composition, you will find yourself reaching for them in every layer of a codebase: validation pipelines, event systems, retry logic, and beyond.
Before Java 8, passing behaviour as a value required an anonymous inner class:</description></item><item><title>LDAP Authentication: Enterprise Directory Integration</title><link>https://blog.devops-monk.com/tutorials/spring-security/ldap-authentication/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/ldap-authentication/</guid><description>LDAP in the Enterprise LDAP (Lightweight Directory Access Protocol) is the standard protocol for directory services. Microsoft Active Directory, OpenLDAP, and many other enterprise identity providers use LDAP. If your application must authenticate users from a corporate directory, LDAP integration is the path.
sequenceDiagram participant Client participant SpringSecurity as Spring Security participant LDAP as LDAP / Active Directory Client->>SpringSecurity: POST /login {username, password} SpringSecurity->>LDAP: Bind as service account (manager DN) LDAP-->>SpringSecurity: Bind success SpringSecurity->>LDAP: Search for user:\n(uid=alice,ou=users,dc=example,dc=com) LDAP-->>SpringSecurity: User DN found SpringSecurity->>LDAP: Bind as user (verify password) LDAP-->>SpringSecurity: Bind success = password correct SpringSecurity->>LDAP: Search groups for user LDAP-->>SpringSecurity: Groups: [cn=developers, cn=devops] SpringSecurity->>SpringSecurity: Map groups to roles SpringSecurity-->>Client: Authentication success (ROLE_DEVELOPER, ROLE_DEVOPS) Dependencies &amp;lt;!</description></item><item><title>Many-to-Many Relationships: @ManyToMany and Join Tables</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/many-to-many-relationships/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/many-to-many-relationships/</guid><description>Introduction A many-to-many relationship means records in table A can relate to many records in table B, and vice versa. Products have many tags; tags apply to many products. In SQL this requires a join table. In JPA, @ManyToMany and @JoinTable handle this automatically — but when you need extra data on the join (like a creation date or a relevance score), you need a different approach.
Simple @ManyToMany: Product and Tag products (*) ──── product_tags ──── (*) tags The product_tags join table has two columns: product_id and tag_id.</description></item><item><title>Message Headers: Metadata, Routing, and Custom Header Propagation</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/message-headers/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/message-headers/</guid><description>What Are Kafka Record Headers? Every Kafka record carries a list of Header objects — key-value pairs of String key and byte[] value. They sit outside the message payload and are ideal for:
Trace propagation — carry X-Trace-Id / X-Span-Id across service boundaries Correlation IDs — link a response to a request in async flows Routing metadata — signal which region, tenant, or feature flag applies Schema type hints — __TypeId__ (set automatically by JsonSerializer) Event versioning — indicate schema version without modifying the payload flowchart LR subgraph Record["</description></item><item><title>Method References: Four Kinds and When to Use Each</title><link>https://blog.devops-monk.com/tutorials/java8/method-references/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/method-references/</guid><description>What Are Method References? Method references are a signal to the reader: &amp;ldquo;this lambda does exactly one thing — it calls this existing method.&amp;rdquo; When the entire body of a lambda is a single method call, replacing it with a method reference removes noise without losing information. Knowing which of the four kinds to apply in each context is what separates fluent Java 8 code from code that merely uses the syntax.</description></item><item><title>Method Security: @PreAuthorize, @PostAuthorize, @Secured</title><link>https://blog.devops-monk.com/tutorials/spring-security/method-security/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/method-security/</guid><description>Why Method Security Exists URL-based authorization in authorizeHttpRequests() protects HTTP endpoints, but it has blind spots:
The same service method may be called from multiple controllers, scheduled tasks, or message listeners — none of which pass through the HTTP filter chain Fine-grained rules based on method arguments or return values cannot be expressed as URL patterns Authorization logic spread across a large security config is hard to read alongside the code it protects Method security moves the authorization decision to the method itself.</description></item><item><title>Migrating to Java 11: From Java 8 — Step by Step</title><link>https://blog.devops-monk.com/tutorials/java11/migration-guide/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/migration-guide/</guid><description>Migration Overview Migrating from Java 8 to Java 11 is the most common Java upgrade scenario. The migration has two distinct phases:
Make it compile and run — fix incompatibilities introduced by Java 9–11 Modernise the code — adopt var, List.of(), String.isBlank(), and other new APIs Phase 1 is mandatory and blocks the upgrade. Phase 2 is optional and can happen incrementally.
This guide focuses entirely on Phase 1.
The 10-Step Migration Checklist [ ] 1.</description></item><item><title>Migrating to Java 17: From Java 8 and Java 11 — Step by Step</title><link>https://blog.devops-monk.com/tutorials/java17/migration-guide/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/migration-guide/</guid><description>Why Migrate to Java 17? Java 17 is the current recommended enterprise LTS. Key reasons to migrate now:
Spring Boot 3.x requires Java 17 — the entire Spring ecosystem is moving here Java 11 extended support ends around 2026 depending on your vendor Java 8 mainstream support ended in 2019; extended support ended 2030 for Oracle JDK but active vulnerability exposure is increasing Language features: Records, Sealed Classes, Text Blocks, Pattern Matching, Switch Expressions — all available in Java 17 Security: Strong JDK encapsulation closes years of internal API exposure used in exploits Recommended Migration Path Java 8 → Java 11 → Java 17 Do not jump directly from Java 8 to Java 17 in one step if your codebase is large or has many third-party dependencies.</description></item><item><title>Migrating to Java 21: From Java 8, 11, and 17 — Step by Step</title><link>https://blog.devops-monk.com/tutorials/java21/migration-guide/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/migration-guide/</guid><description>Why Migrate Now? Java 21 is the current Long-Term Support (LTS) release, and it is the most feature-rich LTS since Java 8. LTS releases receive security patches and bug fixes for years. Java 11, the previous widely-used LTS, reached its extended support window end depending on your vendor. Java 8 mainstream support ended in 2019.
More concretely, Java 21 brings:
Virtual Threads — drop-in replacement for platform threads, enabling massive concurrency without reactive rewrites Pattern Matching for switch and records — eliminating entire categories of verbose, error-prone instanceof/cast chains Sequenced Collections — a unified API for ordered collection types Generational ZGC — sub-millisecond GC pauses at any heap size These are not incremental improvements.</description></item><item><title>Module System (JPMS, JEP 261): Project Jigsaw Deep Dive</title><link>https://blog.devops-monk.com/tutorials/java11/module-system/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/module-system/</guid><description>The Problem JPMS Solves Before Java 9, the JDK had no real notion of encapsulation at the library level. public meant accessible to everyone — including internal JDK classes like sun.misc.Unsafe and com.sun.internal.*. Large codebases suffered from:
No reliable encapsulation: Any public class in any JAR was reachable from any other JAR. Classpath hell: Duplicate or conflicting classes from different JARs led to unpredictable behaviour. Monolithic JDK: The entire 60+ module JDK had to ship with every application.</description></item><item><title>Monitoring: Consumer Lag, Micrometer Metrics, and Actuator Integration</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/monitoring/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/monitoring/</guid><description>What to Monitor in Kafka Production Kafka applications need visibility into:
Consumer lag — how many records are unprocessed per partition Throughput — records produced and consumed per second Error rates — listener exceptions, DLT records, retry counts Producer latency — time from send() to broker acknowledgment Rebalance frequency — high rebalance rate signals consumer instability Dependencies &amp;lt;!-- Micrometer Prometheus registry --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;micrometer-registry-prometheus&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;!-- Spring Boot Actuator --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>Multi-Factor Authentication (TOTP and WebAuthn)</title><link>https://blog.devops-monk.com/tutorials/spring-security/multi-factor-authentication/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/multi-factor-authentication/</guid><description>Why MFA Matters A password alone is a single point of failure. Phishing, credential stuffing, and password reuse mean that knowing a password is not proof of identity. Multi-factor authentication requires something the user knows (password) and something they have (phone, hardware key) — compromising one factor is no longer sufficient.
TOTP: Time-Based One-Time Passwords TOTP (RFC 6238) generates a 6-digit code that changes every 30 seconds, derived from a shared secret and the current time.</description></item><item><title>Multi-Threaded Steps and Async Processing for Performance</title><link>https://blog.devops-monk.com/tutorials/spring-batch/multi-threaded-steps/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/multi-threaded-steps/</guid><description>Introduction A single-threaded Spring Batch step processes one chunk at a time — read N items, process N items, write N items, repeat. For large data sets this is a bottleneck. Spring Batch offers two in-JVM scaling options:
Approach How it works Use when Multi-threaded step Multiple threads each process independent chunks Reader is thread-safe (JdbcPagingItemReader) AsyncItemProcessor Processing runs concurrently; writes remain sequential I/O-bound processors (REST calls, slow enrichment) This article covers both, plus the thread-safety requirements you must meet.</description></item><item><title>New APIs: Base64, StringJoiner, Spliterator, Files.lines, StampedLock, Nashorn</title><link>https://blog.devops-monk.com/tutorials/java8/new-apis/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/new-apis/</guid><description>Base64 Before Java 8, Base64 encoding required a third-party library (Apache Commons Codec, Guava) or the internal sun.misc.BASE64Encoder class — an API that was never officially supported and was deliberately restricted from Java 9 onwards. Java 8 standardised encoding in java.util.Base64, which covers standard, URL-safe, and MIME variants and can wrap streams for large payloads. Java 8 added java.util.Base64 to the standard library.
Three Encoders // Standard Base64 (RFC 4648) Base64.</description></item><item><title>New String Methods (Java 11): isBlank, lines, strip, repeat</title><link>https://blog.devops-monk.com/tutorials/java11/string-methods/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/string-methods/</guid><description>New String Methods in Java 11 Java 11 added six new instance methods to java.lang.String. None require any imports — they are part of the standard String class.
Method Returns Description isBlank() boolean True if string is empty or contains only whitespace lines() Stream&amp;lt;String&amp;gt; Stream of lines split by line terminators strip() String Removes leading and trailing Unicode whitespace stripLeading() String Removes leading Unicode whitespace only stripTrailing() String Removes trailing Unicode whitespace only repeat(int n) String Returns the string repeated n times isBlank() Returns true if the string is empty or contains only whitespace characters as defined by Character.</description></item><item><title>Non-Blocking Retries: @RetryableTopic, BackOff, and the Retry Topic Chain</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/non-blocking-retries/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/non-blocking-retries/</guid><description>The Blocking Retry Problem DefaultErrorHandler retries by seeking back to the failed offset. While retrying, no other records from that partition are consumed — the partition is blocked. For a topic with high throughput, one slow retry can cause significant consumer lag.
flowchart TD subgraph Blocking["Blocking Retry (DefaultErrorHandler)"] B1["poll() → [r50, r51, r52, r53]"] B2["process r50 ✓"] B3["process r51 ✗ — retry"] B4["wait 10s... retry r51 ✗"] B5["wait 20s... retry r51 ✗"</description></item><item><title>OAuth2 Fundamentals: Grant Types and Flows</title><link>https://blog.devops-monk.com/tutorials/spring-security/oauth2-fundamentals/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/oauth2-fundamentals/</guid><description>OAuth2 in One Sentence OAuth2 lets a user grant a third-party application limited access to their account on another service — without giving the third party their password.
Classic example: &amp;ldquo;Sign in with Google.&amp;rdquo; Your app never sees the user&amp;rsquo;s Google password. Google verifies the user&amp;rsquo;s identity and gives your app a token with limited permissions.
The Four OAuth2 Roles flowchart TD RO["**Resource Owner**\nThe user who owns the data\n(e.g. Alice, the Google account owner)"</description></item><item><title>OAuth2 Login: Sign In with Google, GitHub, and Custom Providers</title><link>https://blog.devops-monk.com/tutorials/spring-security/oauth2-login/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/oauth2-login/</guid><description>What OAuth2 Login Does OAuth2LoginConfigurer implements the Authorization Code flow for user authentication. When a user clicks &amp;ldquo;Sign in with Google&amp;rdquo;:
Spring redirects to Google&amp;rsquo;s /authorize endpoint User authenticates on Google and grants consent Google redirects back to your app with a code Spring exchanges the code for tokens (back channel) Spring fetches the user profile from /userinfo Spring creates an OAuth2User and stores it in the SecurityContext Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.</description></item><item><title>OAuth2 Resource Server: Protecting APIs with Bearer Tokens</title><link>https://blog.devops-monk.com/tutorials/spring-security/oauth2-resource-server/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/oauth2-resource-server/</guid><description>What Is a Resource Server? In OAuth2, a Resource Server is an API that accepts access tokens and returns protected resources. It doesn&amp;rsquo;t issue tokens — that&amp;rsquo;s the Authorization Server&amp;rsquo;s job. The Resource Server just validates tokens and enforces access control.
flowchart LR Client[API Client\nor SPA/Mobile] -->|"1. POST /token\n{client_id, secret}"| AS[Authorization Server\nGoogle / Okta / Custom] AS -->|"2. access_token"| Client Client -->|"3. GET /api/products\nAuthorization: Bearer {token}"| RS[Your Spring Boot API\nResource Server] RS -->|"</description></item><item><title>Offset Management: Auto-Commit vs Manual Acknowledgment</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/offset-management/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/offset-management/</guid><description>Why Offset Management Matters The committed offset determines what happens when a consumer restarts. If the offset is committed too early, a crash before processing completes means events are lost. If it is committed too late, a crash after processing but before committing means events are re-processed.
flowchart TD subgraph TooEarly["Commit too early → Data Loss"] E1["Commit offset 43"] --> E2["Process record 42"] --> E3["Crash!"] E4["Restart: resume from 43"] --> E5["</description></item><item><title>One-to-Many and Many-to-One: The Most Common Relationship</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/one-to-many-many-to-one/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/one-to-many-many-to-one/</guid><description>Introduction The one-to-many relationship is the most common in any domain model. An Order has many OrderItems. A Category has many Products. A Customer has many Orders. Understanding @OneToMany and @ManyToOne well — especially bidirectional mapping, collection types, and cascade configuration — is foundational to any JPA application.
The Domain Example orders (1) ──────────────── (*) order_items An order has many order items. Each order item belongs to exactly one order.</description></item><item><title>One-to-One Relationships: @OneToOne in Depth</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/one-to-one-relationships/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/one-to-one-relationships/</guid><description>Introduction A one-to-one relationship means one record in table A corresponds to exactly one record in table B. In JPA this is modelled with @OneToOne. Understanding where the foreign key lives, which side &amp;ldquo;owns&amp;rdquo; the relationship, and how cascade operations work is essential before moving to the more complex @OneToMany and @ManyToMany.
The Domain Example In the e-commerce system, a Customer has one CustomerProfile containing their preferences and biography. A profile belongs to exactly one customer.</description></item><item><title>Optimistic and Pessimistic Locking: Handling Concurrent Updates</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/optimistic-pessimistic-locking/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/optimistic-pessimistic-locking/</guid><description>The Lost Update Problem Two users edit the same product simultaneously:
Time User A User B T1 Load product (price=999) Load product (price=999) T2 Change price to 899 T3 Change price to 799 T4 Save → UPDATE price=899 T5 Save → UPDATE price=799 User A&amp;rsquo;s change is silently overwritten. This is the lost update problem. Both JPA locking strategies prevent it — in different ways.
Optimistic Locking with @Version Optimistic locking assumes conflicts are rare.</description></item><item><title>Optional: Eliminating NullPointerException the Right Way</title><link>https://blog.devops-monk.com/tutorials/java8/optional/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/optional/</guid><description>The Problem Optional Solves NullPointerException is the most common runtime exception in Java, and it is almost always avoidable. Optional&amp;lt;T&amp;gt; is not a magic fix — used incorrectly it becomes a more verbose null check. Used correctly, it encodes the possibility of absence directly in the type system, so callers can never &amp;ldquo;forget&amp;rdquo; to handle the empty case. This article covers both how to use Optional and — critically — how not to.</description></item><item><title>Pagination and Sorting with Pageable, Page, and Slice</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/pagination-sorting/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/pagination-sorting/</guid><description>Why Pagination Matters Returning all rows from a database table is one of the most common production incidents. A query that works in development with 100 rows silently becomes a 10-second, 2 GB memory spike when production has 2 million rows. Pagination limits how many rows travel from the database to the application at a time.
Spring Data JPA makes pagination a first-class feature — pass a Pageable to any repository method and get a Page&amp;lt;T&amp;gt; back.</description></item><item><title>Parallel Streams: ForkJoinPool, Spliterators, and When NOT to Parallelize</title><link>https://blog.devops-monk.com/tutorials/java8/parallel-streams/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/parallel-streams/</guid><description>How Parallel Streams Work Parallel streams are one of Java 8&amp;rsquo;s most misused features. It is tempting to add .parallel() to any slow stream pipeline, but the performance characteristics are counterintuitive: parallel can make things slower for small data, and adding blocking I/O inside a parallel stream can stall the entire JVM. This article explains the mechanics, the cases where parallel genuinely helps, and the patterns to avoid.
A parallel stream splits its source into sub-sequences, processes each sub-sequence on a separate thread, and merges the results.</description></item><item><title>Partitioning: Splitting Work Across Parallel Workers</title><link>https://blog.devops-monk.com/tutorials/spring-batch/partitioning/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/partitioning/</guid><description>Introduction Multi-threaded steps (Article 20) run multiple chunks concurrently from a single reader. Partitioning is different — it splits the data into independent slices before processing starts, then runs each slice as its own StepExecution with its own reader, processor, writer, and metadata.
This gives you:
True independence between partitions — one partition&amp;rsquo;s failure doesn&amp;rsquo;t affect others A separate StepExecution row per partition in BATCH_STEP_EXECUTION — full visibility into per-partition progress The ability to distribute partitions across multiple JVMs (remote partitioning, covered in Article 22) Partitioning Architecture ManagerStep (PartitionStep) │ ├── Partitioner → creates N ExecutionContexts (one per partition) ├── PartitionHandler → distributes partitions to workers │ └── Worker Steps (run per partition) ├── ItemReader → reads only its slice of data ├── ItemProcessor └── ItemWriter The manager step runs once: it calls Partitioner.</description></item><item><title>Password Encoding: BCrypt, Argon2, and DelegatingPasswordEncoder</title><link>https://blog.devops-monk.com/tutorials/spring-security/password-encoding/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/password-encoding/</guid><description>Why Passwords Must Be Hashed Storing plaintext passwords is a critical security failure. When a database is breached, attackers immediately have every user&amp;rsquo;s password — and because people reuse passwords, those credentials work on other sites too.
Password hashing is not encryption. Encryption is reversible. Hashing is one-way: you can verify a password by hashing it and comparing to the stored hash, but you cannot recover the original password from the hash.</description></item><item><title>Password Management: Registration, Reset, and Migration</title><link>https://blog.devops-monk.com/tutorials/spring-security/password-management/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/password-management/</guid><description>Secure Registration Registration is where passwords enter your system. Get it wrong here and no amount of downstream security saves you.
The Registration Flow User submits form → Validate password strength → Check username/email uniqueness → Encode password with PasswordEncoder → Persist user (disabled) → Send verification email → User clicks link → enable account Registration Endpoint @RestController @RequestMapping(&amp;#34;/auth&amp;#34;) public class RegistrationController { private final UserService userService; private final PasswordEncoder passwordEncoder; @PostMapping(&amp;#34;/register&amp;#34;) public ResponseEntity&amp;lt;Void&amp;gt; register(@Valid @RequestBody RegistrationRequest request) { userService.</description></item><item><title>Pattern Matching for instanceof (JEP 394): Smarter Type Checks</title><link>https://blog.devops-monk.com/tutorials/java17/pattern-matching-instanceof/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/pattern-matching-instanceof/</guid><description>Finalized in Java 16 (JEP 394). Available in all Java 16+ releases, including Java 17. Previous previews: Java 14 (JEP 305) and Java 15 (JEP 375).
The Problem: The instanceof-Cast Dance Every Java developer has written this pattern:
// Java 11 — check, then cast if (obj instanceof String) { String s = (String) obj; // cast is logically redundant System.out.println(s.toUpperCase()); } The instanceof check already verified the type. The cast on the next line is conceptually redundant — the compiler could infer that obj is a String in the if body.</description></item><item><title>Pattern Matching for switch (JEP 406): Type Dispatch in switch (Preview)</title><link>https://blog.devops-monk.com/tutorials/java17/pattern-matching-switch-preview/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/pattern-matching-switch-preview/</guid><description>Preview Feature in Java 17 (JEP 406 — First Preview). Requires --enable-preview. This feature evolved through Java 18 (JEP 420), Java 19 (JEP 427), Java 20 (JEP 433), and was finalized in Java 21 (JEP 441). The Java 21 final version has minor syntax differences from this Java 17 preview — documented below.
Enabling Preview Features Pattern matching for switch requires --enable-preview in Java 17. Add to your build tool:</description></item><item><title>Pattern Matching for switch (JEP 441): Type Dispatch Without the Boilerplate</title><link>https://blog.devops-monk.com/tutorials/java21/pattern-matching-switch/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/pattern-matching-switch/</guid><description>The Problem: Cascading instanceof Chains Any Java codebase handling multiple subtypes has code like this:
// Java 16 and earlier — the bad old way static double calculateArea(Shape shape) { if (shape instanceof Circle) { Circle c = (Circle) shape; return Math.PI * c.radius() * c.radius(); } else if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; return r.width() * r.height(); } else if (shape instanceof Triangle) { Triangle t = (Triangle) shape; return 0.</description></item><item><title>Pausing, Resuming, and Stopping Listener Containers</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/listener-lifecycle/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/listener-lifecycle/</guid><description>Why Control Container Lifecycle? A running listener consumes from Kafka continuously. In production you need to:
Pause consumption when a downstream service is overloaded (back-pressure) Resume once the downstream recovers Stop a container entirely during maintenance or feature flag toggles Restart after a configuration change without redeploying Spring Kafka exposes all of this through KafkaListenerEndpointRegistry and the container&amp;rsquo;s own lifecycle API.
Container States stateDiagram-v2 [*] --> Running : start() Running --> Paused : pause() Paused --> Running : resume() Running --> Stopped : stop() Stopped --> Running : start() Paused --> Stopped : stop() Running — polling Kafka, dispatching to listener Paused — broker connection maintained, consumer heartbeat sent, no new records fetched Stopped — consumer thread terminated, partitions released back to group Paused is preferable to stopped for temporary throttling: it avoids a rebalance and keeps the consumer&amp;rsquo;s partition assignment intact.</description></item><item><title>Performance Tuning: Chunk Size, Connection Pools, and Memory Management</title><link>https://blog.devops-monk.com/tutorials/spring-batch/performance-tuning/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/performance-tuning/</guid><description>Introduction A poorly tuned batch job can be 10–100x slower than a well-tuned one. The biggest gains come from a handful of settings — chunk size, MySQL JDBC rewrite, connection pool alignment, and avoiding unnecessary object creation. This article covers each systematically.
Chunk Size — The Most Impactful Setting Chunk size determines how many items are processed per transaction. Too small = too many round trips to the database. Too large = long transactions, high memory pressure, slower rollback on failure.</description></item><item><title>Primary Keys and Generated Values: IDENTITY, SEQUENCE, UUID</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/primary-keys-generated-values/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/primary-keys-generated-values/</guid><description>Introduction Primary key choice affects performance, scalability, and application design. This article covers every strategy JPA supports — from the simple AUTO_INCREMENT to UUID and composite keys — with the trade-offs of each.
IDENTITY Strategy (MySQL AUTO_INCREMENT) GenerationType.IDENTITY delegates key generation to the database column&amp;rsquo;s auto-increment feature. This is the standard for MySQL.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; Generated DDL (when ddl-auto=create):
id BIGINT NOT NULL AUTO_INCREMENT How IDENTITY works with Hibernate IDENTITY has one important behaviour: Hibernate cannot batch INSERT statements when using IDENTITY.</description></item><item><title>Producer @Bean Configuration: Beyond application.properties</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/producer-bean-configuration/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/producer-bean-configuration/</guid><description>Why @Bean Configuration? application.properties is convenient for a single producer, but insufficient when you need:
Multiple producers with different serializers (e.g. one for JSON events, one for Avro) Different settings per environment built at runtime (not just property substitution) Producers sending to different clusters (e.g. primary + DR cluster) Programmatic validation of configuration at startup flowchart TB subgraph PropertiesApproach["application.properties Approach"] P1["Single producer config\nspring.kafka.producer.*\n✓ Simple\n✗ One producer only\n✗ No runtime logic"] end subgraph BeanApproach["</description></item><item><title>Producer Acknowledgments: acks, min.insync.replicas, and Data Durability</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/producer-acknowledgments/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/producer-acknowledgments/</guid><description>What Are Acknowledgments? When a producer sends a record to a Kafka broker, it can wait for confirmation that the write was received and replicated before considering the send &amp;ldquo;complete.&amp;rdquo; The acks setting controls how much confirmation the producer requires.
flowchart LR Producer["Producer"] Leader["Partition Leader\n(Broker 1)"] F1["Follower\n(Broker 2)"] F2["Follower\n(Broker 3)"] Producer -->|"ProduceRequest"| Leader Leader -->|"replicate"| F1 Leader -->|"replicate"| F2 Leader -->|"ProduceResponse ✓"| Producer style Producer fill:#3b82f6,color:#fff style Leader fill:#10b981,color:#fff The acknowledgment is the broker&amp;rsquo;s confirmation to the producer.</description></item><item><title>Producer Retries: Backoff, Timeouts, and Retry Strategies</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/producer-retries/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/producer-retries/</guid><description>Why Producers Need Retries Network errors, leader elections, and broker restarts are normal events in a distributed system. Without retries, a transient broker hiccup causes permanent data loss from the producer&amp;rsquo;s perspective. With retries, the producer automatically re-sends failed records until either the broker accepts them or a timeout deadline is reached.
sequenceDiagram participant Producer participant Leader as Leader (Broker 1) participant NewLeader as New Leader (Broker 2) Producer->>Leader: ProduceRequest (offset 42) Note over Leader: Broker 1 crashes mid-write Leader--xProducer: No response (timeout) Note over Producer: retry.</description></item><item><title>Projections and DTOs: Fetching Only What You Need</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/projections-dtos/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/projections-dtos/</guid><description>The Over-Fetching Problem By default, findAll() loads every column for every entity. A Product entity with 20 fields loads all 20 columns — even when a dropdown menu only needs id and name. This wastes bandwidth, memory, and query time.
Projections let you define what shape the result should take, and Spring Data JPA generates a query that fetches only those columns.
Interface Projections The simplest projection: declare an interface with getter methods matching entity field names.</description></item><item><title>Reactive Security with Spring WebFlux</title><link>https://blog.devops-monk.com/tutorials/spring-security/reactive-security-webflux/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/reactive-security-webflux/</guid><description>Reactive vs. Servlet Security Spring Security&amp;rsquo;s standard configuration targets Servlet-based applications (Spring MVC). Reactive applications built with Spring WebFlux run on a non-blocking event loop — there is no thread-per-request model, so ThreadLocal-based SecurityContextHolder does not work.
Spring Security provides a parallel reactive stack:
Servlet Reactive SecurityFilterChain SecurityWebFilterChain HttpSecurity ServerHttpSecurity SecurityContextHolder ReactiveSecurityContextHolder UserDetailsService ReactiveUserDetailsService AuthenticationManager ReactiveAuthenticationManager @EnableWebSecurity @EnableWebFluxSecurity @EnableMethodSecurity @EnableReactiveMethodSecurity Dependencies &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-webflux&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-security&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; Spring Security auto-configures reactive security when WebFlux is on the classpath.</description></item><item><title>Record Patterns (JEP 440): Destructuring Records with Power and Precision</title><link>https://blog.devops-monk.com/tutorials/java21/record-patterns/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/record-patterns/</guid><description>Records Recap Records (Java 16, JEP 395) are transparent carriers of immutable data:
record Point(int x, int y) {} record ColoredPoint(Point point, String color) {} record Line(Point start, Point end) {} The compiler generates a constructor, accessor methods (x(), y()), equals, hashCode, and toString. Before Java 21, accessing record components required calling accessor methods:
Object obj = new ColoredPoint(new Point(3, 4), &amp;#34;red&amp;#34;); if (obj instanceof ColoredPoint cp) { int x = cp.</description></item><item><title>Records (JEP 395): Immutable Data Classes Without the Boilerplate</title><link>https://blog.devops-monk.com/tutorials/java17/records/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/records/</guid><description>Finalized in Java 16 (JEP 395). Available in all Java 16+ releases, including Java 17. Previous previews: Java 14 (JEP 359) and Java 15 (JEP 384).
The Problem: Data Classes in Java Writing a simple immutable data class in Java 11 requires significant boilerplate:
public final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int x() { return x; } public int y() { return y; } @Override public boolean equals(Object o) { if (this == o) return true; if (!</description></item><item><title>Refresh Tokens and Token Rotation</title><link>https://blog.devops-monk.com/tutorials/spring-security/refresh-tokens/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/refresh-tokens/</guid><description>Why Refresh Tokens? Short-lived access tokens (15–60 minutes) limit the damage if a token is stolen — it expires quickly. But forcing users to log in every hour is terrible UX.
Refresh tokens solve this: a long-lived token (7–30 days) stored securely lets the client silently obtain a new access token when the old one expires. The user stays logged in indefinitely without re-entering credentials.
sequenceDiagram participant Client participant AuthServer as Auth Endpoint participant API as Protected API Client->>AuthServer: POST /api/auth/login AuthServer-->>Client: {accessToken: exp 15min, refreshToken: exp 7d} Note over Client,API: Normal API usage (15 min) Client->>API: GET /api/data\nAuthorization: Bearer {accessToken} API-->>Client: 200 OK Note over Client,API: Access token expires Client->>API: GET /api/data\nAuthorization: Bearer {expiredToken} API-->>Client: 401 Unauthorized Note over Client,API: Silent token refresh Client->>AuthServer: POST /api/auth/refresh\n{refreshToken} AuthServer-->>Client: {newAccessToken, newRefreshToken} Client->>API: GET /api/data\nAuthorization: Bearer {newAccessToken} API-->>Client: 200 OK Token Rotation: Every Refresh Issues a New Refresh Token Token rotation is the critical security mechanism: every time a refresh token is used, the server issues a new refresh token and invalidates the old one.</description></item><item><title>Remote Partitioning and Remote Chunking with Kafka</title><link>https://blog.devops-monk.com/tutorials/spring-batch/remote-partitioning-kafka/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/remote-partitioning-kafka/</guid><description>Introduction Local partitioning (Article 21) runs all workers on one JVM. When a single machine is the bottleneck — CPU, memory, or network bandwidth — you need workers on separate machines. Spring Batch Integration provides two patterns for this:
Pattern What distributes Coordinator controls Workers do Remote Partitioning Partition descriptors (small messages) Data splitting, aggregation Full read-process-write per partition Remote Chunking Actual items (larger messages) Reading Processing + writing only Remote partitioning is the more common choice — workers read directly from the database/file, so only small partition metadata crosses the network.</description></item><item><title>Removed and Deprecated APIs: Java EE, JavaFX, Nashorn (JEP 320, 335, 336)</title><link>https://blog.devops-monk.com/tutorials/java11/removed-deprecated-apis/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/removed-deprecated-apis/</guid><description>Removal Timeline API Deprecated In Removed In JEP Java EE modules (JAXB, JAX-WS, etc.) Java 9 Java 11 JEP 320 JavaFX Bundled with Java 9 Unbundled in Java 11 — Nashorn JavaScript Engine Java 11 Java 15 JEP 335 Pack200 tools and API Java 11 Java 14 JEP 336 javah tool Java 9 Java 10 JEP 313 sun.misc.BASE64Encoder/Decoder Java 8 Java 11 Encapsulation Thread.destroy(), Thread.stop(Throwable) Old Java 11 — Applet API Java 9 Java 17 JEP 289 Java EE Modules Removed (JEP 320) The six Java EE modules that shipped with Java SE 6–10 were removed from Java 11.</description></item><item><title>Repository Interfaces: CrudRepository, JpaRepository, and How They Work</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/repository-interfaces/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/repository-interfaces/</guid><description>Introduction Spring Data JPA&amp;rsquo;s repository abstraction eliminates the DAO boilerplate that every Java application used to require. You declare an interface, extend one of the repository base interfaces, and Spring generates a complete implementation at startup — find, save, delete, count, and more — with zero code written.
The Repository Hierarchy Repository&amp;lt;T, ID&amp;gt; ← Marker interface — no methods │ └── CrudRepository&amp;lt;T, ID&amp;gt; ← Basic CRUD: save, find, delete, count │ └── PagingAndSortingRepository&amp;lt;T, ID&amp;gt; ← + findAll(Pageable), findAll(Sort) │ └── JpaRepository&amp;lt;T, ID&amp;gt; ← + flush, saveAndFlush, deleteInBatch Each interface adds more operations.</description></item><item><title>Request-Reply Pattern with ReplyingKafkaTemplate</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/request-reply/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/request-reply/</guid><description>When Kafka Needs to Be Synchronous Kafka is designed for asynchronous event streaming. But some flows genuinely need a response: a payment validation service that must confirm before the order proceeds, or a pricing engine that must return the current price before checkout completes. ReplyingKafkaTemplate gives you a blocking send-and-receive call over Kafka without leaving the Kafka ecosystem.
How Request-Reply Works sequenceDiagram participant Requester as "Order Service\n(ReplyingKafkaTemplate)" participant Broker participant Replier as "</description></item><item><title>Retryable vs Non-Retryable Exceptions: Custom Exception Classification</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/retryable-exceptions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/retryable-exceptions/</guid><description>Transient vs Permanent Failures Not every exception is worth retrying. Retrying a NullPointerException or a schema validation error wastes time and delays other records. Retrying a database timeout or a downstream HTTP 503 is exactly right — the error is temporary and will likely resolve.
flowchart TD Ex["Exception in listener"] Q{"Transient?\n(DB timeout, HTTP 503,\nnetwork blip)"} Q -->|Yes| Retry["Retry with BackOff"] Q -->|No| Skip["Call recoverer immediately\n(no retries wasted)"] Retry -->|"still failing after\nmax retries"</description></item><item><title>Role-Based Access Control: Roles, Authorities, and Hierarchies</title><link>https://blog.devops-monk.com/tutorials/spring-security/role-based-access-control/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/role-based-access-control/</guid><description>Roles vs. Authorities: The Distinction That Matters Spring Security uses one interface for both roles and authorities: GrantedAuthority. Both are just strings. The difference is convention.
Authority: a fine-grained permission string — user:read, report:export, order:cancel Role: a coarse-grained label that groups authorities — ROLE_ADMIN, ROLE_MANAGER, ROLE_USER The ROLE_ prefix is the only mechanical difference. When you call hasRole(&amp;quot;ADMIN&amp;quot;), Spring Security prepends ROLE_ automatically and checks for ROLE_ADMIN. When you call hasAuthority(&amp;quot;ROLE_ADMIN&amp;quot;) you must include the prefix yourself.</description></item><item><title>Scheduling Batch Jobs: @Scheduled, Quartz, and Clustered Scheduling</title><link>https://blog.devops-monk.com/tutorials/spring-batch/scheduling-batch-jobs/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/scheduling-batch-jobs/</guid><description>Introduction A batch job that only runs manually is barely useful. Production jobs run on a schedule — nightly, hourly, after a file arrives. Spring Boot provides three scheduling options:
Option Persistence Clustered Use when @Scheduled No No Simple cron on a single node Quartz Scheduler Yes (DB) Yes HA scheduling, persistent triggers External scheduler (Kubernetes CronJob, Airflow) Varies Yes Complex pipelines, dependency management @Scheduled — Simple Cron Trigger The simplest approach: annotate a method with @Scheduled and run the job from it.</description></item><item><title>Scoped Values (JEP 446): Thread-Safe Context Without ThreadLocal</title><link>https://blog.devops-monk.com/tutorials/java21/scoped-values/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/scoped-values/</guid><description>Preview Feature — Requires --enable-preview at compile and runtime.
The ThreadLocal Problem at Scale ThreadLocal has been the standard way to pass context (request ID, user session, database transaction) implicitly through a call stack without threading it through every method signature. It works well with a few hundred platform threads. With virtual threads, it breaks down.
Problem 1 — Memory overhead with inheritance
When you create a child thread with InheritableThreadLocal, Java copies the thread-local map from parent to child:</description></item><item><title>Sealed Classes (JEP 409): Controlled, Exhaustive Class Hierarchies</title><link>https://blog.devops-monk.com/tutorials/java17/sealed-classes/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/sealed-classes/</guid><description>Finalized in Java 17 (JEP 409). This is the headline language feature of the Java 17 LTS release. Previous previews: Java 15 (JEP 360) and Java 16 (JEP 397).
The Problem: Open Hierarchies Are Hard to Reason About In Java, any class can be extended by default. This openness is flexible but has costs.
Consider a Shape interface used in a drawing application:
// Java 11 — anyone can implement Shape public interface Shape { double area(); } At runtime, a Shape instance could be a Circle, a Rectangle, a Triangle, or anything else in the classpath.</description></item><item><title>Second-Level Cache and Query Cache with Hibernate</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/second-level-cache/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/second-level-cache/</guid><description>Cache Layers in Hibernate Hibernate has two cache levels:
Level Scope Lifetime Shared? First-level cache One Session (one transaction) Transaction No — per session Second-level cache SessionFactory Application lifetime Yes — across sessions The first-level cache (Article 24) prevents repeated reads within one transaction. The second-level cache prevents repeated reads across transactions — once an entity is loaded, it stays in the shared cache until evicted.
When to Use the Second-Level Cache Good candidates:</description></item><item><title>Security Headers: CSP, HSTS, Clickjacking Protection</title><link>https://blog.devops-monk.com/tutorials/spring-security/security-headers/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/security-headers/</guid><description>Why Security Headers Matter Security headers tell browsers how to behave when handling your content. They stop entire classes of attacks — XSS, clickjacking, protocol downgrade, information leakage — with a few lines of configuration. They cost nothing at runtime and are one of the highest-value-per-effort security improvements available.
Spring Security&amp;rsquo;s Default Headers Spring Security adds a set of secure headers by default. You do not need any explicit configuration to get them:</description></item><item><title>Security: TLS 1.3, ChaCha20-Poly1305, and Curve25519 (JEP 329, 332, 324)</title><link>https://blog.devops-monk.com/tutorials/java11/security-tls13/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/security-tls13/</guid><description>Security Improvements Across Java 9–11 JEP Release Feature JEP 287 Java 9 SHA-3 hash algorithms JEP 273 Java 9 DRBG-based SecureRandom JEP 288 Java 9 Disable SHA-1 certificates JEP 324 Java 11 Key Agreement with Curve25519 and Curve448 JEP 329 Java 11 ChaCha20 and Poly1305 cryptographic algorithms JEP 332 Java 11 Transport Layer Security (TLS) 1.3 JEP 181 Java 11 Nest-Based Access Control TLS 1.3 (JEP 332) TLS 1.3 is the current version of the Transport Layer Security protocol, finalised in RFC 8446 (August 2018).</description></item><item><title>SecurityContext and Authentication Object</title><link>https://blog.devops-monk.com/tutorials/spring-security/security-context-authentication/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/security-context-authentication/</guid><description>The SecurityContext Is the Source of Truth Every security decision in Spring Security ultimately comes down to one question: &amp;ldquo;What Authentication object is stored in the SecurityContext?&amp;rdquo;
Does the user have ROLE_ADMIN? → check Authentication.getAuthorities() What is the user&amp;rsquo;s ID? → cast Authentication.getPrincipal() to UserDetails Is the user logged in at all? → check Authentication.isAuthenticated() Understanding the SecurityContext and Authentication is not optional — it underlies everything.
The Object Hierarchy classDiagram class SecurityContextHolder { -strategy: SecurityContextHolderStrategy +getContext() SecurityContext +setContext(SecurityContext context) +clearContext() +getContextHolderStrategy() SecurityContextHolderStrategy } class SecurityContext { &lt;> +getAuthentication() Authentication +setAuthentication(Authentication authentication) } class Authentication { &lt;> +getPrincipal() Object +getCredentials() Object +getAuthorities() Collection~GrantedAuthority~ +getDetails() Object +isAuthenticated() boolean +getName() String } class UsernamePasswordAuthenticationToken { +UsernamePasswordAuthenticationToken(principal, credentials) +UsernamePasswordAuthenticationToken(principal, credentials, authorities) } class GrantedAuthority { &lt;> +getAuthority() String } class SimpleGrantedAuthority { -role: String +getAuthority() String } SecurityContextHolder --> SecurityContext SecurityContext --> Authentication Authentication &lt;|.</description></item><item><title>SecurityFilterChain Bean: The Modern Configuration API</title><link>https://blog.devops-monk.com/tutorials/spring-security/security-filter-chain-config/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/security-filter-chain-config/</guid><description>The Modern Configuration Model Spring Security 6.x dropped WebSecurityConfigurerAdapter. The new model uses a SecurityFilterChain @Bean directly. This is not just a syntax change — it&amp;rsquo;s a fundamentally cleaner design:
Old approach New approach Extend WebSecurityConfigurerAdapter @Bean SecurityFilterChain method Override configure(HttpSecurity) Accept HttpSecurity parameter Chain with .and() Lambda DSL — each concern is a separate block One class per application Multiple beans, one per URL namespace Implicit global AuthenticationManager Explicit AuthenticationManager bean // OLD — don&amp;#39;t do this @Configuration public class OldSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.</description></item><item><title>Seeking to Specific Offsets: Replay, Recovery, and Time-Based Seeking</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/seeking-offsets/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/seeking-offsets/</guid><description>Why Seek Instead of Reset? Offset management (auto-commit vs manual acknowledgment) controls when offsets advance during normal processing. Seeking is different: it lets you reposition the consumer to any offset — past or future — programmatically, without touching the committed offset in __consumer_offsets.
Common scenarios:
Replay from the beginning — reprocess all historical events after a bug fix Resume from a known-good offset — skip a poison pill that&amp;rsquo;s blocking the consumer Time-based replay — reprocess everything since yesterday 09:00 Startup positioning — always start from the end, ignoring backlog on first launch How Kafka Seeking Works flowchart LR subgraph Broker["</description></item><item><title>Sending Messages with Keys, Headers, and Custom Partitioning</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/producer-keys-headers-partitioning/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/producer-keys-headers-partitioning/</guid><description>Why Partitioning Strategy Matters How you route messages to partitions determines:
Ordering: only messages in the same partition are ordered relative to each other Parallelism: how evenly work is distributed across consumers Hot spots: if one key generates 90% of traffic, one partition (and one consumer) gets 90% of the load flowchart TD subgraph Routing["Message Routing Decision"] Msg["Message"] HasKey{Has key?} HasPartition{Explicit partition?} KeyHash["hash(key) % numPartitions\n→ deterministic, same partition always"] RoundRobin["Sticky partitioning\n(batch to same partition,\nthen round-robin)"</description></item><item><title>Sequenced Collections (JEP 431): A Unified API for Ordered Collections</title><link>https://blog.devops-monk.com/tutorials/java21/sequenced-collections/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/sequenced-collections/</guid><description>The 30-Year Gap in the Collections API Java&amp;rsquo;s Collections Framework has a fundamental inconsistency: there is no uniform way to access the first or last element of an ordered collection. Before Java 21:
// List — index-based var list = List.of(&amp;#34;a&amp;#34;, &amp;#34;b&amp;#34;, &amp;#34;c&amp;#34;); String first = list.get(0); // first element String last = list.get(list.size() - 1); // last element — verbose, error-prone // Deque — special methods Deque&amp;lt;String&amp;gt; deque = new ArrayDeque&amp;lt;&amp;gt;(List.</description></item><item><title>Session Management: Fixation, Concurrency, and Redis Sessions</title><link>https://blog.devops-monk.com/tutorials/spring-security/session-management/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/session-management/</guid><description>How Spring Security Uses Sessions For form login and traditional web applications, Spring Security stores the Authentication object in the HTTP session. On every request, SecurityContextPersistenceFilter (Spring Security 5) or SecurityContextHolderFilter (Spring Security 6) loads the SecurityContext from the session and puts it in the SecurityContextHolder.
For stateless APIs using JWT or OAuth2 Bearer tokens, no session is created — the token is verified on every request.
Session Creation Policy Control when Spring Security creates sessions:</description></item><item><title>Setting Up Java 11: JDK Options, Maven/Gradle, and IDE Configuration</title><link>https://blog.devops-monk.com/tutorials/java11/java11-setup/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/java11-setup/</guid><description>Choosing a JDK Distribution Java 11 is available from multiple OpenJDK distributions. All are TCK-certified and binary-compatible; the differences are support timelines and add-ons.
Distribution Vendor Java 11 Support Until Notes Eclipse Temurin Adoptium Oct 2027 Community standard, most downloaded Amazon Corretto Amazon Aug 2024 (free), extended via AWS Best choice for AWS workloads Azul Zulu Azul Systems 2026+ Wide platform coverage Microsoft Build of OpenJDK Microsoft 2024 Best for Azure Oracle JDK 11 Oracle Oct 2024 (free LTS) Commercial support until 2026 GraalVM CE 21 Oracle/GraalVM Community Native image + polyglot; JDK 21-based For most teams: Eclipse Temurin 11 — widest platform support, longest free support window, backed by a vendor-neutral foundation.</description></item><item><title>Setting Up Java 17: JDK Options, Tooling, and IDE Configuration</title><link>https://blog.devops-monk.com/tutorials/java17/java17-setup/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/java17-setup/</guid><description>JDK Distribution Options Multiple vendors ship Java 17 JDK builds. All pass the TCK (Technology Compatibility Kit) — they are functionally equivalent for development.
Distribution Provider Notes Eclipse Temurin Adoptium / Eclipse Recommended default; fully open-source Amazon Corretto AWS Free; optimized for AWS Lambda and EC2 Microsoft Build of OpenJDK Microsoft Windows and Azure optimized Oracle JDK 17 Oracle Free for development; commercial license for production Azul Zulu Azul Commercial support available; free binaries GraalVM CE 22.</description></item><item><title>Setting Up Java 21: JDK Options, Tooling, and IDE Configuration</title><link>https://blog.devops-monk.com/tutorials/java21/java21-setup/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/java21-setup/</guid><description>Choosing a JDK Distribution Java 21 is open source (OpenJDK). Multiple vendors distribute binaries, all built from the same source with identical features. Choose based on your support requirements:
Distribution Vendor Free LTS Support Notes Eclipse Temurin Adoptium Yes 2028+ Recommended for most developers OpenJDK Oracle/Community Yes 6 months only Reference implementation, no LTS Oracle JDK Oracle Yes (NFTC) 2031 Identical to OpenJDK since Java 17 Amazon Corretto AWS Yes 2030+ Good for AWS deployments Microsoft Build of OpenJDK Microsoft Yes Long-term Good for Azure deployments Azul Zulu Azul Yes/Paid Long-term Includes Zing JVM variant GraalVM Oracle Yes Long-term Adds native-image, polyglot Recommendation: Eclipse Temurin for local development; match your cloud provider&amp;rsquo;s JDK for production (Corretto on AWS, Microsoft OpenJDK on Azure).</description></item><item><title>Setting Up Java 8: JDK Options, Maven/Gradle, and IDE Configuration</title><link>https://blog.devops-monk.com/tutorials/java8/java8-setup/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/java8-setup/</guid><description>Choosing a Java 8 Distribution Getting your environment right before writing a single line of code saves hours of debugging compiler errors and version conflicts later. This article covers every distribution option, all three major build tools, and both popular IDEs — including the specific settings that trip up most developers when first configuring Java 8.
Oracle stopped providing free Java 8 updates for commercial use in January 2019. Multiple vendors now ship free, production-quality Java 8 builds:</description></item><item><title>Setting Up Spring Boot with Spring Data JPA and MySQL</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/setup-spring-boot-spring-data-jpa/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/setup-spring-boot-spring-data-jpa/</guid><description>Introduction This article builds the project foundation used throughout the entire series. By the end, you will have a running Spring Boot 3.3 application connected to MySQL 8.x with Hibernate 6, a proper connection pool, schema management via Flyway, and SQL logging configured so you can see exactly what Hibernate sends to the database.
Project Setup Maven pom.xml &amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt; &amp;lt;project xmlns=&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34; xmlns:xsi=&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34; xsi:schemaLocation=&amp;#34;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&amp;gt; &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt; &amp;lt;parent&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;3.</description></item><item><title>Specifications and the Criteria API: Dynamic Queries</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/specifications-criteria-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/specifications-criteria-api/</guid><description>Why Specifications Derived query methods and @Query annotations work for fixed queries. But what happens when the user can filter by any combination of 10 fields — some optional, some not?
// Wrong approach — combinatorial explosion List&amp;lt;Product&amp;gt; findByName(String name); List&amp;lt;Product&amp;gt; findByNameAndPrice(String name, BigDecimal price); List&amp;lt;Product&amp;gt; findByNameAndPriceAndCategory(...); // ... you&amp;#39;d need 2^10 = 1024 methods The Specification pattern solves this. Each filter condition is a reusable Specification&amp;lt;T&amp;gt; object. You compose them with and(), or(), and not() at runtime, and Spring Data JPA generates the correct query.</description></item><item><title>Spring Authorization Server: Build Your Own OAuth2 Server</title><link>https://blog.devops-monk.com/tutorials/spring-security/spring-authorization-server/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/spring-authorization-server/</guid><description>What Is Spring Authorization Server? Spring Authorization Server (SAS) is an official Spring project that implements OAuth 2.1 and OpenID Connect 1.0 as a Spring Boot application. It provides a complete authorization server you can host yourself — issuing tokens for your own clients and APIs.
Use it when:
You want SSO across your own microservices You cannot use a hosted provider (Okta, Auth0) due to compliance or cost You need complete control over token format and claims You&amp;rsquo;re building a platform where other apps authenticate against your identity service Architecture flowchart TD SPA[SPA / Mobile App\nOAuth2 Client] -->|Authorization Code + PKCE| SAS Service[Backend Service\nClient Credentials| SAS] SAS[Spring Authorization Server\nIssues tokens] RS1[Your REST API\nResource Server] -->|Validates JWT| SAS RS2[Another API\nResource Server] -->|Validates JWT| SAS SAS -->|Signs tokens with| PK[Private Key\nJWK Set at /.</description></item><item><title>Spring Kafka Production Checklist and Best Practices</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/production-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/production-best-practices/</guid><description>Before You Ship This is the checklist distilled from everything in this series. Work through it before your first production deployment. Each item links to the article where it&amp;rsquo;s covered in depth.
Producer Checklist Durability # Never lose data on leader failure spring.kafka.producer.acks=all # At least 2 brokers must acknowledge every write spring.kafka.producer.properties.min.insync.replicas=2 # Enables exactly-once message delivery (required for transactions) spring.kafka.producer.properties.enable.idempotence=true Do: Set acks=all and min.insync.replicas=2 for any topic that carries business data.</description></item><item><title>Spring Security Best Practices and Production Checklist</title><link>https://blog.devops-monk.com/tutorials/spring-security/spring-security-best-practices/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/spring-security-best-practices/</guid><description>Using This Reference This is the final article in the Spring Security series. It is a consolidated reference — not a tutorial. Come back to this checklist before every launch and when reviewing a new codebase. Each item links back to the relevant article in the series.
1. Keep Spring Security Updated Security vulnerabilities in Spring Security itself are rare but severe when they occur. A dependency that is one minor version behind can expose known CVEs.</description></item><item><title>Starting a Kafka Cluster: Single-Broker and 3-Broker with KRaft</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-cluster-setup/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/kafka-cluster-setup/</guid><description>Prerequisites Docker Desktop installed and running docker compose v2 (bundled with Docker Desktop 4.x+) Ports 9092, 9093, 9094 free on your machine All articles in this series assume a running local Kafka cluster. Start with the single-broker setup for articles 1–6, then switch to the 3-broker cluster when we cover replication and fault tolerance.
Single-Broker Cluster (Development) This is the simplest setup — one Kafka node running in combined mode (broker + controller).</description></item><item><title>Stream &amp; Optional API Enhancements (Java 9–11)</title><link>https://blog.devops-monk.com/tutorials/java11/stream-optional-enhancements/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/stream-optional-enhancements/</guid><description>Stream API — New Methods (Java 9) Java 9 added four new instance methods to Stream&amp;lt;T&amp;gt;. All are terminal-ish or intermediate operations that make common patterns expressible without workarounds.
takeWhile(Predicate) Returns elements from the beginning of the stream as long as the predicate holds, then stops. The first element that fails the predicate (and all subsequent elements) are discarded.
Stream.of(1, 2, 3, 4, 5, 1, 2) .takeWhile(n -&amp;gt; n &amp;lt; 4) .</description></item><item><title>Streams API: Lazy Pipelines and the Functional Data Model</title><link>https://blog.devops-monk.com/tutorials/java8/streams-introduction/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java8/streams-introduction/</guid><description>The Problem Streams Solve Java loops are not wrong — they are often the right tool. But when a computation is a multi-step pipeline of filter → transform → aggregate, a loop hides the structure of the computation in a tangle of temporary variables and mutated collections. The Streams API makes the structure explicit, lets the JVM optimise it, and composes naturally with lambdas and method references.
Consider filtering a list of orders to find the names of active premium customers who spent over $500, sorted alphabetically:</description></item><item><title>Structured Concurrency (JEP 453): Safe, Readable Concurrent Code</title><link>https://blog.devops-monk.com/tutorials/java21/structured-concurrency/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/structured-concurrency/</guid><description>Preview Feature — Requires --enable-preview at compile and runtime. The API stabilized significantly from Java 21 through 24 and will be finalized in a future release.
The Unstructured Concurrency Problem When you submit tasks to an ExecutorService, the tasks are logically related but structurally unconnected — the executor doesn&amp;rsquo;t know they belong together:
// Unstructured concurrency — looks simple, hides serious problems ExecutorService executor = Executors.newCachedThreadPool(); Future&amp;lt;Order&amp;gt; orderFuture = executor.</description></item><item><title>Switch Expressions (JEP 361): Switch as an Expression, Not Just a Statement</title><link>https://blog.devops-monk.com/tutorials/java17/switch-expressions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/switch-expressions/</guid><description>Finalized in Java 14 (JEP 361). Available in all Java 14+ releases, including Java 17. Previous previews: Java 12 (JEP 325) and Java 13 (JEP 354).
The Problem with Switch Statements Java&amp;rsquo;s traditional switch statement has two well-known pain points:
Pain 1: Fall-Through // Java 11 — easy to introduce fall-through bugs switch (day) { case MONDAY: result = &amp;#34;Start of work week&amp;#34;; break; // easy to forget this case TUESDAY: result = &amp;#34;Weekday&amp;#34;; // no break — falls through to WEDNESDAY case case WEDNESDAY: result = &amp;#34;Hump day&amp;#34;; break; default: result = &amp;#34;Other&amp;#34;; } A missing break causes the next case to execute unexpectedly.</description></item><item><title>Testing Kafka Applications: EmbeddedKafka and Testcontainers</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/testing-kafka/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/testing-kafka/</guid><description>Two Testing Strategies Strategy Speed Fidelity Use when @EmbeddedKafka Fast (~2s startup) In-process broker, not 100% identical Unit/integration tests — CI fast path KafkaContainer (Testcontainers) Slower (~10s startup) Real Kafka broker in Docker Acceptance tests, DLT/transaction validation Use both: @EmbeddedKafka for the bulk of tests, KafkaContainer for the smoke suite that validates real-broker behaviour.
Test Dependencies &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.kafka&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-kafka-test&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.testcontainers&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;kafka&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.awaitility&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;awaitility&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; @EmbeddedKafka — Fast Integration Tests Testing a Producer @SpringBootTest @EmbeddedKafka( partitions = 1, topics = {&amp;#34;orders&amp;#34;}, brokerProperties = {&amp;#34;log.</description></item><item><title>Testing Spring Data JPA: @DataJpaTest, Testcontainers, and Best Practices</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/testing-spring-data-jpa/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/testing-spring-data-jpa/</guid><description>Why Test JPA Code? Unit tests with mocked repositories verify your service logic but nothing about the database. They won&amp;rsquo;t catch:
Wrong column mapping Constraint violations N+1 queries introduced by a new lazy association Transactions that silently don&amp;rsquo;t roll back Queries that fail on the real database but pass on H2 Testing against a real database — or at minimum a database-compatible in-memory store — is necessary. Spring Boot&amp;rsquo;s @DataJpaTest and Testcontainers make this practical.</description></item><item><title>Testing Spring Security: @WithMockUser, MockMvc, and SecurityMockMvc</title><link>https://blog.devops-monk.com/tutorials/spring-security/testing-spring-security/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/testing-spring-security/</guid><description>Why Security Tests Are Non-Negotiable A security configuration that is never tested is a security configuration that is probably wrong. URL patterns have subtle ordering rules. Method security annotations are silently ignored if @EnableMethodSecurity is missing. CSRF tokens must be present in the right form. Testing is the only way to know your authorization rules are actually enforced.
Test Dependencies &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; &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; @WithMockUser The simplest way to simulate an authenticated user.</description></item><item><title>Text Blocks (JEP 378): Multiline Strings Without the Escape Hell</title><link>https://blog.devops-monk.com/tutorials/java17/text-blocks/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java17/text-blocks/</guid><description>Finalized in Java 15 (JEP 378). Available in all Java 15+ releases, including Java 17. Previous previews: Java 13 (JEP 355) and Java 14 (JEP 368).
The Problem: Embedded Strings in Java Writing multi-line strings in Java has always been painful:
// JSON — a wall of escape sequences and concatenation String json = &amp;#34;{\n&amp;#34; + &amp;#34; \&amp;#34;name\&amp;#34;: \&amp;#34;Alice\&amp;#34;,\n&amp;#34; + &amp;#34; \&amp;#34;role\&amp;#34;: \&amp;#34;engineer\&amp;#34;,\n&amp;#34; + &amp;#34; \&amp;#34;active\&amp;#34;: true\n&amp;#34; + &amp;#34;}&amp;#34;; // SQL — unreadable indentation and newlines String sql = &amp;#34;SELECT u.</description></item><item><title>The N+1 Problem: Detection, Root Cause, and All Solutions</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/n-plus-one-problem-solutions/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/n-plus-one-problem-solutions/</guid><description>What Is the N+1 Problem? The N+1 problem occurs when loading a list of N entities triggers N additional queries to load their associations — one query per entity.
Example: load 50 orders, then access each order&amp;rsquo;s customer:
SELECT * FROM orders; -- 1 query SELECT * FROM customers WHERE id = 1; -- query for order 1&amp;#39;s customer SELECT * FROM customers WHERE id = 2; -- query for order 2&amp;#39;s customer SELECT * FROM customers WHERE id = 3; -- query for order 3&amp;#39;s customer .</description></item><item><title>The Persistence Context: How JPA Tracks Your Entities</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/persistence-context-entity-lifecycle/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/persistence-context-entity-lifecycle/</guid><description>Introduction The persistence context is the single most important concept in JPA. Everything else — lazy loading, dirty checking, transaction scope, detached entity exceptions — only makes sense once you understand what the persistence context is and how it works.
Many JPA bugs come from developers not understanding this concept. Understand it well and the rest of JPA becomes predictable.
What Is the Persistence Context? The persistence context is an in-memory map of entities managed by the current EntityManager.</description></item><item><title>The Security Filter Chain: Every Filter Explained</title><link>https://blog.devops-monk.com/tutorials/spring-security/security-filter-chain/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/security-filter-chain/</guid><description>Every Filter Has a Job The Spring Security filter chain is not a monolith — it&amp;rsquo;s 15-20 individual filters, each responsible for exactly one concern. Understanding each filter&amp;rsquo;s job means you can:
Know exactly where in the chain a request fails Add your own filter in the right position Remove filters you don&amp;rsquo;t need for performance Debug authentication and authorization problems The Complete Filter Order Spring Security defines a strict ordering for its filters.</description></item><item><title>Tooling: JShell, jlink, and Single-File Programs (JEP 222, 282, 330)</title><link>https://blog.devops-monk.com/tutorials/java11/tooling-jshell-jlink/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/tooling-jshell-jlink/</guid><description>Overview Java 9 and 11 added three tools that change how you write, test, and deploy Java code:
Tool JEP Release Purpose JShell JEP 222 Java 9 Interactive REPL for Java code jlink JEP 282 Java 9 Build minimal custom JRE images Single-file programs JEP 330 Java 11 Run .java files directly JShell — Interactive Java REPL JShell is Java&amp;rsquo;s Read-Eval-Print Loop: a command-line tool for evaluating Java expressions, statements, methods, and classes interactively, without creating a project.</description></item><item><title>Transaction Boundaries and Common Pitfalls</title><link>https://blog.devops-monk.com/tutorials/spring-data-jpa/transaction-boundaries-pitfalls/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-data-jpa/transaction-boundaries-pitfalls/</guid><description>Transaction Boundaries A transaction boundary is the point where a transaction starts and where it ends. In Spring, boundaries are defined by @Transactional on service methods.
HTTP Request └── Controller (no transaction) └── @Transactional Service method ← transaction OPENS here ├── Repository call 1 ├── Repository call 2 └── method returns ← transaction COMMITS here Everything inside the @Transactional method runs in the same database transaction. The persistence context (first-level cache) lives for the duration of that transaction.</description></item><item><title>Unnamed Classes and Instance Main Methods (JEP 445): Java for Scripts and Beginners</title><link>https://blog.devops-monk.com/tutorials/java21/unnamed-classes/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/unnamed-classes/</guid><description>Preview Feature — Requires --enable-preview at compile and runtime.
The Boilerplate Problem Teaching Java to a beginner means explaining class declarations, access modifiers, static, and String[] before they can print &amp;ldquo;Hello, World!&amp;rdquo;:
// Traditional Java — 4 concepts before printing one line public class HelloWorld { public static void main(String[] args) { System.out.println(&amp;#34;Hello, World!&amp;#34;); } } Every word carries meaning, but that meaning is irrelevant until the student understands OOP, the JVM class model, and program entry points.</description></item><item><title>Unnamed Patterns and Variables (JEP 443): Writing Intent-Clear Code</title><link>https://blog.devops-monk.com/tutorials/java21/unnamed-patterns/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/unnamed-patterns/</guid><description>Preview Feature — Requires --enable-preview at compile and runtime. The _ identifier was reserved in Java 9; this JEP gives it formal semantics.
The Problem: Forced Naming of Unused Things When pattern matching, you often care about only some components of a matched structure. But the syntax forces you to name everything:
record Point(int x, int y) {} record ColoredPoint(Point point, String color) {} record Box(ColoredPoint cp, int weight) {} // You only care about the color — but must name everything else if (obj instanceof Box(ColoredPoint(Point(int x, int y), String color), int weight)) { System.</description></item><item><title>var Keyword (JEP 286, 323): Local Variable Type Inference</title><link>https://blog.devops-monk.com/tutorials/java11/var-keyword/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java11/var-keyword/</guid><description>What var Does var is a reserved type name (not a keyword) introduced in Java 10 (JEP 286). It instructs the compiler to infer the type of a local variable from its initialiser. The inferred type is fixed at compile time — var does not make Java dynamically typed.
// Before var ArrayList&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;String&amp;gt;(); Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; scores = new HashMap&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt;(); // With var var names = new ArrayList&amp;lt;String&amp;gt;(); var scores = new HashMap&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt;(); After compilation, both forms produce identical bytecode.</description></item><item><title>Vector API (JEP 448): SIMD Computation in Java</title><link>https://blog.devops-monk.com/tutorials/java21/vector-api/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/vector-api/</guid><description>Preview Feature in Java 21 — The Vector API has been in preview since Java 16 (JEP 338). JEP 448 is the sixth preview iteration in Java 21. The API is stable and production-usable with --enable-preview; finalization is pending Project Valhalla value types.
What Is SIMD and Why Does It Matter? Modern CPUs can perform the same arithmetic operation on multiple data values in a single instruction. This is called SIMD — Single Instruction, Multiple Data.</description></item><item><title>Virtual Threads (JEP 444): A Million Threads Without the Pain</title><link>https://blog.devops-monk.com/tutorials/java21/virtual-threads/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/java21/virtual-threads/</guid><description>The Platform Thread Problem Every Java thread since Java 1.0 maps 1:1 to an OS thread. OS threads are heavy:
Stack memory: 512 KB – 2 MB per thread (configurable with -Xss, default 512 KB on Linux) Context switch cost: ~1–10 μs per switch (kernel mode transition + cache invalidation) Hard limit: A 64-bit machine with 8 GB RAM can support roughly 8,000–16,000 OS threads before running out of stack memory This limit shapes how Java servers are built.</description></item><item><title>What Is Apache Kafka: Event Streaming From First Principles</title><link>https://blog.devops-monk.com/tutorials/spring-kafka/what-is-apache-kafka/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-kafka/what-is-apache-kafka/</guid><description>The Problem Kafka Solves Imagine an e-commerce platform. A customer places an order. What needs to happen next?
Inventory must be reserved Payment must be charged A confirmation email must be sent The warehouse must be notified to pick and pack Analytics must record the sale Fraud detection must evaluate the transaction One request. Six downstream systems. In a traditional REST architecture, the Order Service calls each of those six services directly — synchronously, one after another.</description></item><item><title>X.509 Certificate Authentication</title><link>https://blog.devops-monk.com/tutorials/spring-security/x509-certificate-authentication/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-security/x509-certificate-authentication/</guid><description>What Is X.509 Authentication? X.509 authentication (also called mutual TLS or mTLS) uses digital certificates instead of passwords. The client presents a certificate during the TLS handshake. The server verifies the certificate against a trusted Certificate Authority (CA) and extracts the user identity from the certificate&amp;rsquo;s Common Name (CN) or Subject Alternative Name (SAN).
sequenceDiagram participant Client as Client (with certificate) participant Server as Spring Boot Server Client->>Server: TLS ClientHello Server->>Client: TLS ServerHello + Server Certificate Server->>Client: CertificateRequest (ask for client cert) Client->>Server: Client Certificate + CertificateVerify Server->>Server: Verify against trusted CA Server->>Server: Extract CN from certificate: "</description></item><item><title>Adopting Liquibase on an Existing Production Database</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-existing-database-adoption/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-existing-database-adoption/</guid><description>Most teams don&amp;rsquo;t start with Liquibase. You inherit a production database that&amp;rsquo;s been running for years, modified by scripts, hotfixes, and well-intentioned developers who &amp;ldquo;just ran it manually.&amp;rdquo; Now you want version control. You want repeatable deployments. You want to stop the &amp;ldquo;did you run the migration?&amp;rdquo; Slack message.
The good news: you can adopt Liquibase without touching production data. The bad news: it requires care — not complexity, just care.</description></item><item><title>Advanced Processing: CompositeItemProcessor, External APIs, and Async Processing</title><link>https://blog.devops-monk.com/tutorials/spring-batch/advanced-processors/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/advanced-processors/</guid><description>Introduction Real batch jobs often need more than one transformation step per item. You might validate first, then normalize, then enrich, then convert to the output type. CompositeItemProcessor chains multiple single-responsibility processors together. For I/O-bound enrichment steps, AsyncItemProcessor runs processing concurrently on a thread pool — giving you parallelism without rewriting your step as multi-threaded.
CompositeItemProcessor CompositeItemProcessor chains a list of processors. The output of each processor becomes the input to the next.</description></item><item><title>Advanced Writers: JpaItemWriter, CompositeItemWriter, and Custom Writers</title><link>https://blog.devops-monk.com/tutorials/spring-batch/advanced-writers/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/advanced-writers/</guid><description>Introduction The previous article covered the two most common writers: FlatFileItemWriter for files and JdbcBatchItemWriter for database inserts. This article covers advanced scenarios:
Persisting JPA entities with JpaItemWriter Writing to multiple destinations simultaneously with CompositeItemWriter Routing items to different writers based on content with ClassifierCompositeItemWriter Building custom writers for REST APIs, message queues, and cloud storage Combining a writer with a post-step cleanup tasklet JpaItemWriter JpaItemWriter calls EntityManager.merge() on each item.</description></item><item><title>API Documentation with OpenAPI and Springdoc</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-openapi-springdoc/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-cloud-gateway/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-api-versioning/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-configuration-profiles/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-async-virtual-threads/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-bean-validation/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Best Practices Reference: The Complete Liquibase Checklist</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-best-practices/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-best-practices/</guid><description>Twenty-three articles of Liquibase knowledge, distilled into one reference. Bookmark this page. Use the checklists before every production deployment. Come back when something breaks.
This article doesn&amp;rsquo;t repeat the &amp;ldquo;why&amp;rdquo; — that&amp;rsquo;s in the earlier articles. This is the what: concrete rules, with the article number if you need the full explanation.
The 7 Laws (Non-Negotiable) These apply without exception. Break them and you will eventually have a bad day in production.</description></item><item><title>Building a Modular Monolith with Spring Modulith</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-spring-modulith/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-rest-api/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-caching/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-cloud-config-server/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/liquibase-changelog-formats/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/liquibase-changelog-organization/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Chunk-Oriented Processing: The Core Spring Batch Pattern</title><link>https://blog.devops-monk.com/tutorials/spring-batch/chunk-oriented-processing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/chunk-oriented-processing/</guid><description>The read-process-write loop is at the heart of almost every Spring Batch job. Understanding exactly how it works — where transactions begin and end, what gets rolled back on failure, and how Spring Batch knows where to restart — makes everything else in the framework click into place.
This article goes deep on chunk-oriented processing: the execution model, the three interfaces, the counters that track progress, and how to size chunks correctly.</description></item><item><title>CI/CD Integration: GitHub Actions Pipeline for Database Deployments</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-cicd-github-actions/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-cicd-github-actions/</guid><description>The Liquibase commands covered in Articles 5 and 16 become reliable only when they run automatically on every change. A developer who remembers to run futureRollbackSQL before merging is better than one who doesn&amp;rsquo;t — but a pipeline that enforces it is better than both.
This article builds a complete GitHub Actions pipeline: PR validation gates that block merges when rollback is missing, a staging deployment workflow, and a production deployment workflow with mandatory tagging and pre-generated rollback files.</description></item><item><title>Claude Extended and Adaptive Thinking: Making Claude Reason Before It Answers</title><link>https://blog.devops-monk.com/2026/05/claude-adaptive-thinking-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/claude-adaptive-thinking-guide/</guid><description>By default, Claude generates its response token by token without any deliberate planning step. For most tasks — answering a question, writing a function, explaining a concept — this is fine. The response comes quickly and it is good.
For some tasks, it is not enough. Complex multi-step reasoning problems, ambiguous architecture decisions, intricate security analyses — these benefit from Claude thinking through the problem before committing to an answer. That is what extended thinking and adaptive thinking provide.</description></item><item><title>Claude Managed Agents: Deploy AI Agents Without Managing Infrastructure</title><link>https://blog.devops-monk.com/2026/05/claude-managed-agents-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/claude-managed-agents-guide/</guid><description>Building an AI agent that runs autonomously — browses the web, executes code, reads and writes files, persists memory across sessions — requires infrastructure. You need a sandbox, a process that can run for hours without your web server timing out, and a way to resume from where you left off after a network hiccup.
Claude Managed Agents, launched in public beta in April 2026, offloads all of that to Anthropic.</description></item><item><title>Claude Models in 2026: Opus, Sonnet, and Haiku Compared</title><link>https://blog.devops-monk.com/2026/05/claude-models-guide-2026/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/claude-models-guide-2026/</guid><description>Picking the wrong Claude model is expensive. Opus on every task costs 5x more than Sonnet for comparable results on most work. Haiku on a complex reasoning task produces worse output than just asking Sonnet. And if you are still using models from early 2025, some of them are deprecated — or will be soon.
This guide covers every current Claude model, what each is good at, how much they cost, and a concrete decision framework for choosing the right one.</description></item><item><title>Claude Prompt Caching: Cut Your API Costs by 90%</title><link>https://blog.devops-monk.com/2026/05/claude-prompt-caching-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/claude-prompt-caching-guide/</guid><description>If you are calling the Claude API repeatedly with a large system prompt, a big document, or a long codebase context — and you are not using prompt caching — you are paying full price every time for content that has not changed. Prompt caching stores a prefix of your prompt server-side and charges 90% less to read it back on every subsequent request.
For applications that repeatedly process the same context, this is the single highest-impact API optimisation available.</description></item><item><title>Common Migration Patterns: 12 Real-World Schema Changes with MySQL</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-migration-patterns/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Configuring Jobs and Steps: Flows, Decisions, and Conditional Execution</title><link>https://blog.devops-monk.com/tutorials/spring-batch/job-step-configuration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/job-step-configuration/</guid><description>Introduction So far every example has had a single step. Real batch jobs usually have multiple steps — validate input, import data, generate a report, send a notification, clean up temp files. Spring Batch&amp;rsquo;s JobBuilder DSL lets you compose these steps into flows with conditional branching, parallel execution, and decision logic.
This article covers:
Linear multi-step jobs Conditional step transitions using ExitStatus JobExecutionDecider for runtime branching Parallel flows with split Nested jobs with FlowStep The Flow abstraction for reusable step sequences Linear Multi-Step Job The simplest multi-step job runs steps in sequence.</description></item><item><title>Contexts and Labels: Multi-Environment Filtering</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-contexts-labels/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/liquibase-core-commands/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/liquibase-core-concepts/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-data-jpa-crud/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-flyway-migrations/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Diff, Snapshot, and Reverse Engineering: Onboarding Existing Databases</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-diff-reverse-engineering/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-diff-reverse-engineering/</guid><description>Most Liquibase tutorials start with a blank database. Most real projects start with a production database that has been evolving for years without version control. This article covers the complete workflow for adopting Liquibase on an existing database — generating a baseline changelog, bootstrapping Liquibase tracking, detecting drift between environments, and maintaining snapshots for offline comparisons.
The Onboarding Problem An existing database has no DATABASECHANGELOG table. Running liquibase update with a fresh changelog would try to create tables that already exist, causing immediate failures.</description></item><item><title>Dockerizing Spring Boot Applications</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-docker/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-dto-response/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-data-jpa-relationships/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Event-Driven Spring Boot: Transactional Outbox Pattern with Kafka</title><link>https://blog.devops-monk.com/2026/05/spring-boot-kafka-transactional-outbox/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-kafka-transactional-outbox/</guid><description>Publishing an event to Kafka after saving to the database looks simple. It has a subtle, dangerous flaw: if the Kafka publish fails after the DB commit, or the app crashes between the two, your event is lost and your data is inconsistent.
The Transactional Outbox Pattern solves this by writing the event to the database in the same transaction as the business data, then publishing to Kafka separately. This guide covers the pattern, the implementation, and idempotent consumers.</description></item><item><title>Externalized Configuration with @ConfigurationProperties</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-configuration-properties/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-observability/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-exception-handling/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 with Spring Boot 4: From 8 Seconds to 37ms Startup</title><link>https://blog.devops-monk.com/2026/05/spring-boot-graalvm-native-images/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-graalvm-native-images/</guid><description>Spring Boot applications running as GraalVM native images start in milliseconds, use a fraction of the memory, and fit in tiny containers. The tradeoff is a longer build time. In 2026, with Spring Boot 4 and GraalVM 24, native images are production-ready for most Spring applications.
This guide covers everything: what Spring AOT does, how to build your first native image, how to fix the common issues, and how to add native builds to CI.</description></item><item><title>GraalVM Native Images: Millisecond Startup</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-graalvm-native/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-production-readiness/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-request-handling/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 Actually Works (Behind the Magic)</title><link>https://blog.devops-monk.com/2026/05/spring-boot-auto-configuration-deep-dive/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-auto-configuration-deep-dive/</guid><description>&amp;ldquo;Spring Boot is magic&amp;rdquo; is something you hear a lot. Add spring-boot-starter-data-jpa and suddenly you have a working DataSource, a JpaTransactionManager, and a LocalContainerEntityManagerFactoryBean — without writing a single @Bean method. Understanding how this actually works turns the magic into a tool you can control, debug, and extend.
The Entry Point: @EnableAutoConfiguration @SpringBootApplication is a shorthand for three annotations:
@Configuration @EnableAutoConfiguration // this is the one that matters here @ComponentScan public class MyApplication { public static void main(String[] args) { SpringApplication.</description></item><item><title>How Spring Boot Auto-Configuration Works — The Magic Explained</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-auto-configuration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-integration-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-cloud-feign-restclient/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-kafka-introduction/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 Batch: What, Why, and Architecture</title><link>https://blog.devops-monk.com/tutorials/spring-batch/introduction-to-spring-batch/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/introduction-to-spring-batch/</guid><description>Every application has a class of work that doesn&amp;rsquo;t fit the request-response model: process 2 million orders overnight, generate 500,000 monthly statements, migrate 10 years of legacy data before Monday morning. This work needs to be reliable, restartable after failures, and fast enough to finish in the available window. That&amp;rsquo;s what Spring Batch is built for.
This article covers what Spring Batch is, when to use it, and how its architecture works.</description></item><item><title>Introduction to Spring Boot — What It Is and Why It Exists</title><link>https://blog.devops-monk.com/tutorials/spring-boot/introduction-to-spring-boot/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-data-jpa-introduction/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/introduction-to-liquibase/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>JobParameters, ExecutionContext, and Job Restartability</title><link>https://blog.devops-monk.com/tutorials/spring-batch/job-parameters-execution-context/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/job-parameters-execution-context/</guid><description>Introduction Two mechanisms let you pass information into and through a Spring Batch job:
JobParameters — input values provided at launch time (a date, a file path, a run ID). They are immutable and persisted to BATCH_JOB_EXECUTION_PARAMS. ExecutionContext — a key-value map that steps can read and write during execution. It is persisted after each chunk commit, enabling restartability. Understanding both is essential for building jobs that can be safely re-run, restarted after failure, and parameterised for different data sets.</description></item><item><title>JobRepository and Batch Metadata: How Spring Batch Tracks Everything</title><link>https://blog.devops-monk.com/tutorials/spring-batch/job-repository-batch-metadata/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/job-repository-batch-metadata/</guid><description>Introduction Every time Spring Batch runs a job it records the run&amp;rsquo;s history in a set of relational tables. This metadata is not optional — it is what makes Spring Batch reliable. Without it there would be no restart capability, no duplicate-run prevention, and no audit trail. Understanding the metadata layer is essential for debugging failures, building monitoring dashboards, and designing restartable jobs.
In this article you will learn:
The role of JobRepository and JobExplorer in the Spring Batch architecture The six metadata tables — their schema, purpose, and relationships How Spring Batch uses these tables to enable restartability How to query batch history directly in MySQL How to access metadata programmatically with JobExplorer Key changes in Spring Batch 5 (removed MapJobRepositoryFactoryBean, new testing approach) All examples use Spring Boot 3.</description></item><item><title>JPA Entity Mapping: @Entity, @Id, @Column, and More</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-data-jpa-entity-mapping/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-jpa-performance-tuning/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-data-jpa-queries/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-security-jwt/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Kubernetes Deployments: Jobs, Init Containers, and Helm Hooks</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-kubernetes-deployment/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-kubernetes-deployment/</guid><description>Spring Boot&amp;rsquo;s Liquibase auto-run works fine for a single instance. In Kubernetes, where multiple pods start simultaneously, auto-run at application startup creates a race: every pod acquires DATABASECHANGELOGLOCK, one holds it, the rest wait, and if Kubernetes kills a pod mid-migration (because it failed its readiness probe while waiting on the lock), the lock remains set and blocks every subsequent pod.
The solution is to run migrations exactly once, before application pods start, using either an init container or a Helm pre-upgrade Job.</description></item><item><title>Listeners: Hooking into Job, Step, and Chunk Lifecycle Events</title><link>https://blog.devops-monk.com/tutorials/spring-batch/listeners/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/listeners/</guid><description>Introduction Spring Batch emits lifecycle events at every stage of execution — before and after a job runs, before and after each step, before and after each chunk, and before and after each individual read/process/write call. Listeners let you hook into these events without modifying your core batch logic.
Common uses:
Log start/end times and item counts Send success/failure notifications (Slack, email, PagerDuty) Publish metrics to Prometheus or CloudWatch Log every skipped item to a dead-letter table Reset resources before a step begins Listener Hierarchy Job ├── JobExecutionListener beforeJob / afterJob │ └── Step ├── StepExecutionListener beforeStep / afterStep ├── ChunkListener beforeChunk / afterChunk / afterChunkError ├── ItemReadListener beforeRead / afterRead / onReadError ├── ItemProcessListener beforeProcess / afterProcess / onProcessError └── ItemWriteListener beforeWrite / afterWrite / onWriteError └── SkipListener onSkipInRead / onSkipInWrite / onSkipInProcess JobExecutionListener @Component public class ImportJobListener implements JobExecutionListener { private static final Logger log = LoggerFactory.</description></item><item><title>Logging: SLF4J, Logback, and Structured Logging</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-logging/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/microservices-architecture-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.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://blog.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>MySQL-Specific Patterns: Character Sets, Engines, Large Table Migrations</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-mysql-specific-patterns/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-mysql-specific-patterns/</guid><description>Liquibase&amp;rsquo;s change types generate standard SQL. MySQL has quirks — character set requirements that differ from the standard, a storage engine option absent from other databases, row format constraints that affect index key length, and DDL locking behaviour that makes a 10-second table lock acceptable in dev and catastrophic in production.
This article covers the MySQL-specific patterns that you will need once you move from tutorial projects to real production databases.</description></item><item><title>Null Safety with JSpecify</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-jspecify-null-safety/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-security-oauth2-authorization-server/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-security-oauth2-resource-server/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>OWASP Top 10 for Spring Boot: Real Vulnerabilities and How to Fix Them</title><link>https://blog.devops-monk.com/2026/05/spring-boot-owasp-security/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-owasp-security/</guid><description>The OWASP Top 10 lists the most critical web application security risks. Spring Boot apps have their own common failure patterns: exposed Actuator endpoints, secrets in properties files, SQL built from string concatenation, and Spring Security misconfiguration.
This guide covers the vulnerabilities that actually appear in Spring Boot applications and how to fix each one.
1. SQL Injection SQL injection remains one of the most critical vulnerabilities. It allows attackers to manipulate database queries.</description></item><item><title>Pagination and Sorting in Spring Boot</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-pagination-sorting/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-security-authentication/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/liquibase-preconditions/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-kafka-producer-consumer/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/liquibase/liquibase-property-substitution/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Reading Flat Files: CSV, Fixed-Width, and Delimited with FlatFileItemReader</title><link>https://blog.devops-monk.com/tutorials/spring-batch/reading-flat-files/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/reading-flat-files/</guid><description>Introduction Flat files — CSV exports, fixed-width mainframe feeds, pipe-delimited data dumps — are the most common input source for batch jobs. Spring Batch&amp;rsquo;s FlatFileItemReader handles all of them. It is restartable out of the box: it persists its line-number position in the ExecutionContext so that a restarted job resumes exactly where it crashed.
In this article you will build a complete order-import job that reads a CSV file and inserts rows into a MySQL orders table.</description></item><item><title>Reading from External Sources: REST APIs, S3, and Custom ItemReaders</title><link>https://blog.devops-monk.com/tutorials/spring-batch/reading-external-sources/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/reading-external-sources/</guid><description>Introduction Not all batch input comes from files or databases. You may need to pull orders from an e-commerce API, sync products from a supplier feed, or process CSV exports stored in Amazon S3. Spring Batch provides MultiResourceItemReader for S3 files and a clean ItemReader interface for anything else.
In this article you will build:
A custom ItemReader that pages through a REST API An S3 reader that downloads files on demand A composite reader that merges multiple sources into one step The ItemReader Contract The entire ItemReader interface is one method:</description></item><item><title>Reading from MySQL: JdbcCursorItemReader and JdbcPagingItemReader</title><link>https://blog.devops-monk.com/tutorials/spring-batch/reading-from-mysql/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/reading-from-mysql/</guid><description>Introduction Most real batch jobs read from a database, not a file. Spring Batch provides two JDBC readers for this:
Reader Strategy Thread-safe Use when JdbcCursorItemReader Open a server-side cursor, stream rows No Single-threaded step, huge result sets JdbcPagingItemReader Execute LIMIT / OFFSET queries in a loop Yes Multi-threaded steps, sorted data Both handle restartability automatically and both work with any DataSource — including MySQL.
JdbcCursorItemReader How it works The reader opens a single JDBC ResultSet on Step.</description></item><item><title>Reading with JPA: JpaPagingItemReader and Entity-Based Reading</title><link>https://blog.devops-monk.com/tutorials/spring-batch/reading-with-jpa/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/reading-with-jpa/</guid><description>Introduction When your application already uses JPA/Hibernate, JpaPagingItemReader lets you read data using JPQL queries and mapped entities instead of raw JDBC. You get the full object graph, type-safe queries, and familiar entity lifecycle — but you also inherit JPA&amp;rsquo;s pitfalls: the N+1 problem, session-per-read overhead, and first-level cache growth.
This article covers:
When to choose JpaPagingItemReader over JdbcPagingItemReader Setting up the reader with JPQL and named queries Fetching associations to avoid N+1 Clearing the persistence context to prevent memory leaks A complete order-processing example with MySQL When to Use JpaPagingItemReader Use it when:</description></item><item><title>Reliable Event Publishing: The Transactional Outbox Pattern</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-kafka-outbox-pattern/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-cloud-resilience4j/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Retry Logic: Handling Transient Failures Gracefully</title><link>https://blog.devops-monk.com/tutorials/spring-batch/retry-logic/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/retry-logic/</guid><description>Introduction Batch jobs interact with databases, REST APIs, and file systems — all of which fail transiently. A MySQL deadlock resolves itself in milliseconds. A network timeout to an external service clears up in seconds. Retrying these transient failures automatically is far better than failing the entire job and requiring a manual restart.
Spring Batch has built-in retry support at the step level, integrated with its transaction management. This article covers everything you need to configure robust retry behaviour.</description></item><item><title>Role-Based Access Control with @PreAuthorize</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-security-rbac/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 in Production: Tag-Based Strategies and CI Validation</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-production-rollback/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-production-rollback/</guid><description>Article 9 covered what rollback commands exist. This article covers how to make rollback reliable in production — where a slow or broken rollback costs real money and real users. The difference between knowing the commands and having a working rollback strategy is process: mandatory tagging, pre-generated rollback files, CI gates, and a decision tree that removes guesswork during an incident.
Why Rollback Fails in Production Rollback failures in production share a small set of root causes:</description></item><item><title>Rollback Strategies: Automatic, Custom, Tag-Based, and Count-Based</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-rollback-strategies/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-cloud-eureka-service-discovery/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Skip Logic, Dead Letter Patterns, and Job Restart Strategies</title><link>https://blog.devops-monk.com/tutorials/spring-batch/skip-logic-restartability/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/skip-logic-restartability/</guid><description>Introduction Retry handles transient failures. Skip handles permanent ones — bad data rows, constraint violations, malformed records that will never succeed no matter how many times you retry. Skip logic lets your job continue processing good records while recording bad ones for human review.
This article covers:
Configuring skip for specific exception types Custom SkipPolicy for fine-grained control Dead-letter table pattern for tracking skipped items Stopping a job intentionally vs failing it Handling abandoned executions Designing jobs that restart safely after any failure Basic Skip Configuration return new StepBuilder(&amp;#34;importOrdersStep&amp;#34;, jobRepository) .</description></item><item><title>Spring AI 2.0: Build a RAG Application with Spring Boot</title><link>https://blog.devops-monk.com/2026/05/spring-ai-rag-application/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-ai-rag-application/</guid><description>Spring AI 1.0 GA shipped in May 2025. It brings the Spring programming model to AI development: a unified ChatClient API that works across Claude, OpenAI, Gemini, Ollama, and Azure OpenAI — switching AI providers is changing one dependency.
This guide builds a complete RAG (Retrieval-Augmented Generation) application that answers questions about your documentation using any AI provider.
What Is RAG? A large language model (LLM) knows everything in its training data but nothing about your specific documents, code, or business data.</description></item><item><title>Spring AI: Build a RAG Application</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-spring-ai-rag/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-bean-scopes-lifecycle/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 + Spring Batch Setup: Your First Complete Batch Project</title><link>https://blog.devops-monk.com/tutorials/spring-batch/spring-boot-spring-batch-setup/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/spring-boot-spring-batch-setup/</guid><description>Article 2 showed how chunk processing works conceptually. This article sets up the real project you&amp;rsquo;ll build on for the rest of the series — complete Maven configuration, MySQL metadata tables, multiple ways to trigger jobs, and correct use of JobParameters. Everything in this article is production-ready from the start.
Project Dependencies Add spring-boot-starter-batch to your Maven project. That one starter brings in everything you need.
&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt; &amp;lt;project xmlns=&amp;#34;http://maven.</description></item><item><title>Spring Boot 2.x → 3.x → 4.x Migration: The Definitive Checklist</title><link>https://blog.devops-monk.com/2026/05/spring-boot-migration-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-migration-guide/</guid><description>Many teams are still running Spring Boot 2.7.x. Spring Boot 2.x reached end of life in November 2023, which means no more security patches. The jump to 4.0 is two generations, and the breaking changes are real — but they are also well-documented and mostly automatable.
This guide walks through the migration in stages: 2.x → 3.0 first, then 3.x incremental updates, then 4.0. Each section lists what breaks and how to fix it.</description></item><item><title>Spring Boot 4.0: Everything That Changed (Complete Guide)</title><link>https://blog.devops-monk.com/2026/05/spring-boot-4-complete-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-4-complete-guide/</guid><description>Spring Boot 4.0 was released on November 20, 2025. It is built on Spring Framework 7 and represents the most significant shift in the Spring ecosystem since the Jakarta EE migration in Spring Boot 3. The headline change is full modularisation — the single spring-boot-autoconfigure JAR has been split into 70+ granular modules. But that is just the start.
This guide covers every change that matters, what breaks on upgrade, and what is genuinely new and useful.</description></item><item><title>Spring Boot Actuator: Health, Metrics, and Management Endpoints</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-actuator/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 Actuator: Production Monitoring with Prometheus and Grafana</title><link>https://blog.devops-monk.com/2026/05/spring-boot-actuator-prometheus-grafana/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-actuator-prometheus-grafana/</guid><description>Spring Boot Actuator exposes production-ready operational endpoints — health checks, metrics, environment info, thread dumps — out of the box. Combined with Prometheus and Grafana, you get a full monitoring stack with minimal configuration.
This guide covers everything from initial setup to Kubernetes health probes, custom metrics, and securing your management endpoints.
Setup Dependencies &amp;lt;dependencies&amp;gt; &amp;lt;!-- Actuator --&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-actuator&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;!-- Micrometer Prometheus registry --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;micrometer-registry-prometheus&amp;lt;/artifactId&amp;gt; &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;/dependencies&amp;gt; Basic configuration # application.</description></item><item><title>Spring Boot Caching: Multi-Level Cache with Caffeine + Redis</title><link>https://blog.devops-monk.com/2026/05/spring-boot-caching-caffeine-redis/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-caching-caffeine-redis/</guid><description>Caching reduces database load and response latency. Spring Boot&amp;rsquo;s cache abstraction lets you add caching with annotations, then swap the implementation (Caffeine, Redis, multi-level) without changing your business code.
This guide covers Caffeine for in-JVM caching, Redis for distributed caching, and a multi-level cache that combines both.
Spring Cache Abstraction Spring&amp;rsquo;s cache abstraction uses three annotations:
Annotation Behaviour @Cacheable Cache the return value. On subsequent calls, return from cache without executing the method.</description></item><item><title>Spring Boot Docker: Multi-Stage Builds, Layered JARs, and Buildpacks</title><link>https://blog.devops-monk.com/2026/05/spring-boot-docker-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-docker-guide/</guid><description>There are three ways to containerise a Spring Boot application: a naive single-stage Dockerfile, a proper multi-stage Dockerfile with layered JARs, and Cloud Native Buildpacks. Each has different tradeoffs in build speed, image size, and maintenance overhead.
This guide covers all three approaches, explains why layered JARs matter for CI/CD speed, and shows how to produce small, secure, production-ready images.
The Problem with the Naive Dockerfile Most tutorials show this:</description></item><item><title>Spring Boot Full Observability: Prometheus + Grafana + Tempo + Loki</title><link>https://blog.devops-monk.com/2026/05/spring-boot-observability-stack/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-observability-stack/</guid><description>Observability means you can answer &amp;ldquo;what is wrong and why&amp;rdquo; from your system&amp;rsquo;s outputs alone — without adding new instrumentation after an incident. It requires three types of data: metrics (what happened), traces (why it happened), and logs (the details).
Spring Boot 4 ships a single OpenTelemetry starter that covers all three. This guide shows how to wire up the complete observability stack: Prometheus + Grafana for metrics, Grafana Tempo for distributed tracing, and Grafana Loki for logs.</description></item><item><title>Spring Boot Integration: Zero-Config Setup and Full Properties Reference</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-spring-boot-integration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 JPA Performance: Solving N+1, Lazy Loading, and Query Optimization</title><link>https://blog.devops-monk.com/2026/05/spring-boot-jpa-performance/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-jpa-performance/</guid><description>JPA makes database access simple. It also makes it dangerously easy to write code that fires 100 SQL queries to load 10 records. The N+1 problem alone has caused more production performance incidents than almost any other JPA issue.
This guide covers how to find and fix the five most common JPA performance problems: N+1 queries, LazyInitializationException, over-fetching, poor connection pool sizing, and Hibernate 6 breaking changes.
Enable SQL Logging First Before optimizing anything, see exactly what queries are firing:</description></item><item><title>Spring Boot Microservices Full Stack: Eureka + Gateway + Config Server + Resilience4j</title><link>https://blog.devops-monk.com/2026/05/spring-boot-microservices-full-stack/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-microservices-full-stack/</guid><description>Building microservices requires more than splitting a monolith into services. You need service discovery, a gateway to route traffic, centralised configuration management, and resilience patterns to handle inevitable failures. Spring Cloud provides all of this as a cohesive stack.
This guide builds a complete microservices architecture from scratch: Eureka for service discovery, Spring Cloud Gateway for routing, Spring Cloud Config Server for centralised config, and Resilience4j for circuit breakers and retry.</description></item><item><title>Spring Boot OAuth2 + JWT: End-to-End Zero-Trust API Security</title><link>https://blog.devops-monk.com/2026/05/spring-boot-oauth2-jwt-security/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-oauth2-jwt-security/</guid><description>Zero-trust API security means every request is validated independently — no session state, no &amp;ldquo;trusted network&amp;rdquo; assumptions. A JWT bearer token is issued by an authorisation server, signed cryptographically, and validated on every API call. The API never calls back to the authorisation server during validation; it verifies the token&amp;rsquo;s signature locally.
This guide covers the complete setup: dependencies, resource server configuration, token validation (both symmetric and asymmetric), extracting claims, role-based access control, method-level security, and the Spring Security 7 changes that break existing setups.</description></item><item><title>Spring Boot on Kubernetes</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-kubernetes/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 on Kubernetes: Health Checks, Graceful Shutdown, and Config Management</title><link>https://blog.devops-monk.com/2026/05/spring-boot-kubernetes-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-kubernetes-guide/</guid><description>Running Spring Boot on Kubernetes is not just packaging the app in a container and deploying it. You need to configure health probes correctly, handle graceful shutdown so in-flight requests don&amp;rsquo;t get dropped, manage configuration without baking secrets into images, and make sure the JVM respects container memory limits.
This guide covers the production-critical Kubernetes configuration for Spring Boot applications.
Health Probes Kubernetes uses three probe types to manage pod lifecycle:</description></item><item><title>Spring Boot Project Structure Explained</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-project-structure/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 Boot Testing with Testcontainers: The Right Way</title><link>https://blog.devops-monk.com/2026/05/spring-boot-testcontainers/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-testcontainers/</guid><description>Testcontainers spins up real Docker containers for your tests — a real PostgreSQL database, a real Redis, a real Kafka broker. No more mocking JDBC connections or in-memory H2 databases that behave differently from production.
Spring Boot 3.1 added @ServiceConnection, which removes the boilerplate of configuring connection URLs manually. This guide covers the right patterns for fast, reliable integration tests with Testcontainers.
Why Testcontainers Over H2 Teams use H2 in-memory databases for testing because it&amp;rsquo;s fast.</description></item><item><title>Spring Boot Virtual Threads: Benchmarks, Pitfalls, and When NOT to Use Them</title><link>https://blog.devops-monk.com/2026/05/spring-boot-virtual-threads/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-virtual-threads/</guid><description>Virtual Threads landed in Java 21 as a stable feature, and Spring Boot 3.2 added first-class support with a single property. The promise: write simple blocking code and get WebFlux-level throughput. The reality is mostly true — with some important exceptions.
This article covers what Virtual Threads actually are, how to enable them in Spring Boot, real benchmark numbers, the three pitfalls that will silently destroy your performance, and a decision framework for when to use them (and when not to).</description></item><item><title>Spring Boot vs Quarkus in 2026: An Honest Benchmarked Comparison</title><link>https://blog.devops-monk.com/2026/05/spring-boot-vs-quarkus/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-vs-quarkus/</guid><description>Every year, someone asks: &amp;ldquo;Should we use Spring Boot or Quarkus?&amp;rdquo; In 2026, both frameworks are mature, both run natively, and both work well on Kubernetes. The differences come down to developer experience, ecosystem size, and specific performance characteristics.
This is an honest comparison with real numbers, not marketing claims.
The Frameworks at a Glance Spring Boot 4 (November 2025): Built on Spring Framework 7. The de-facto standard for enterprise Java.</description></item><item><title>Spring Data with PostgreSQL</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-postgresql/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-ioc-dependency-injection/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 Modulith: Build a Modular Monolith Before You Commit to Microservices</title><link>https://blog.devops-monk.com/2026/05/spring-modulith-guide/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-modulith-guide/</guid><description>Microservices solve real problems: independent deployability, team autonomy, technology flexibility. They also create real problems: distributed transactions, network latency, operational complexity. Many teams split into microservices too early, before they understand their domain well enough to draw stable boundaries.
Spring Modulith gives you module boundaries, enforced isolation, and event-driven decoupling inside a single deployable JAR. It&amp;rsquo;s the pragmatic middle ground.
The Modular Monolith Problem It Solves A typical Spring Boot monolith looks like this after a year:</description></item><item><title>Spring Security Fundamentals: Filter Chain, Authentication, and Authorization</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-security-fundamentals/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Stored Procedures, Views, and Triggers in MySQL</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-stored-objects-mysql/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-stored-objects-mysql/</guid><description>Tables and columns are straightforward in Liquibase — one change type per operation, automatic rollback, clean history. Stored procedures, views, and triggers are different. They contain body SQL that uses semicolons internally, which conflicts with Liquibase&amp;rsquo;s default statement splitting. They are replaced rather than altered. And unlike tables, modifying them frequently over time is expected.
This article covers the mechanics and patterns that make stored objects manageable in Liquibase.
The Core Problem: Delimiter Conflicts A stored procedure body contains semicolons to terminate each statement inside it:</description></item><item><title>Tasklets: Running Non-Chunk Operations in Batch Jobs</title><link>https://blog.devops-monk.com/tutorials/spring-batch/tasklets/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/tasklets/</guid><description>Introduction Not every step in a batch job reads-processes-writes a stream of items. Sometimes you need to:
Delete yesterday&amp;rsquo;s temp files before reading today&amp;rsquo;s data Truncate a staging table before loading new data Call a stored procedure to aggregate results Send an email or Slack notification after processing Execute a DDL statement to add an index These operations do not have items — they are single units of work. Spring Batch handles them with the Tasklet interface.</description></item><item><title>Team Collaboration: Naming Conventions, Conflict Prevention, Git Workflow</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-team-collaboration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-team-collaboration/</guid><description>A solo developer can treat Liquibase conventions as suggestions. A team of five cannot. When two developers both write &amp;ldquo;the next migration&amp;rdquo; on the same day, without a convention that prevents collision, you get duplicate IDs, merge conflicts on the master changelog, or — worst case — two changesets that silently overwrite each other in DATABASECHANGELOG.
This article is the operating agreement for teams using Liquibase: what conventions to establish, how to structure the git workflow, what the PR review checklist looks like, and what to do when conflicts happen despite the conventions.</description></item><item><title>Testing Migrations: H2 vs Testcontainers vs Real MySQL</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-testing-migrations/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-testing-migrations/</guid><description>The CI pipeline from Article 19 validates migrations against a MySQL service container. But that&amp;rsquo;s not the same as testing that your application code works correctly after the migration runs. This article covers how to structure Spring Boot tests that validate both the migration and the application behaviour — and makes the case for replacing H2 with Testcontainers as your primary testing database.
Three Testing Strategies Strategy Database Speed Fidelity Use For H2 in-memory H2 (in-memory) Fastest Low — MySQL-specific SQL fails Unit tests that mock repositories H2 with MySQL mode H2 (MySQL compat) Fast Medium — most SQL works, some quirks Basic integration tests on simple schemas Testcontainers Real MySQL 8.</description></item><item><title>Testing Secured Endpoints</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-security-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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 Batch Jobs: Unit Tests, Integration Tests, and @SpringBatchTest</title><link>https://blog.devops-monk.com/tutorials/spring-batch/testing-spring-batch/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/testing-spring-batch/</guid><description>Introduction Spring Batch jobs are code — they need tests. Without tests you will catch errors in production. With tests you catch them in CI.
Spring Batch has a dedicated testing module that provides @SpringBatchTest, JobLauncherTestUtils, and JobRepositoryTestUtils. These tools let you:
Run a complete job in a test and assert on its BatchStatus Launch individual steps and inspect StepExecution counters Test readers, processors, and writers in complete isolation This article covers all three levels: unit, integration, and database integration with Testcontainers.</description></item><item><title>Testing Spring Boot Apps: Unit Tests with JUnit 5 and Mockito</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-unit-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-repository-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/spring-boot-web-layer-testing/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Top 50 Spring Boot Interview Questions for 2026 (With Detailed Answers)</title><link>https://blog.devops-monk.com/2026/05/spring-boot-interview-questions/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/05/spring-boot-interview-questions/</guid><description>These are the questions that actually come up in Spring Boot interviews — at startups, scale-ups, and large enterprises. Each answer explains the concept clearly, with the level of depth an interviewer expects from a mid-level or senior developer.
Questions are grouped by topic. For junior roles, focus on sections 1–3. For senior roles, everything here is fair game.
Section 1: Core Fundamentals Q1. What is the difference between Spring and Spring Boot?</description></item><item><title>Transactions: @Transactional, Propagation, and Isolation Levels</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-transactions/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Transforming Data with ItemProcessor: Validation, Filtering, and Enrichment</title><link>https://blog.devops-monk.com/tutorials/spring-batch/item-processor/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/item-processor/</guid><description>Introduction ItemProcessor&amp;lt;I, O&amp;gt; sits between the reader and the writer. It receives one item at a time and returns a transformed item — or null to filter the item out entirely. Processors are optional: if your job reads and writes the same type with no transformation needed, you can omit them.
The processor interface is one method:
public interface ItemProcessor&amp;lt;I, O&amp;gt; { O process(I item) throws Exception; } Return a processed item to pass it to the writer.</description></item><item><title>Troubleshooting: 10 Common Liquibase Errors and How to Fix Them</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-troubleshooting/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-troubleshooting/</guid><description>Liquibase errors tend to cluster around the same ten problems. You will hit most of them at least once. This article gives you the exact error message to search for, the root cause, and the fastest fix — so you spend minutes recovering, not hours debugging.
Error 1: DATABASECHANGELOGLOCK — &amp;ldquo;Waiting for changelog lock&amp;rdquo; Symptoms Waiting for changelog lock.... Waiting for changelog lock.... Waiting for changelog lock.... liquibase.exception.LockException: Could not acquire change log lock.</description></item><item><title>What's New in Spring Boot 4.0</title><link>https://blog.devops-monk.com/tutorials/spring-boot/spring-boot-4-whats-new/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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>Writing to Files and Databases: FlatFileItemWriter and JdbcBatchItemWriter</title><link>https://blog.devops-monk.com/tutorials/spring-batch/writing-files-databases/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/spring-batch/writing-files-databases/</guid><description>Introduction After reading and processing data, your batch job needs to write results somewhere. Spring Batch provides two essential writers:
FlatFileItemWriter — writes items to CSV or any delimited/formatted flat file JdbcBatchItemWriter — writes items to a database using JDBC batch inserts/updates Both are transactional and restartable. This article covers both in depth, plus transaction semantics you must understand to avoid duplicates and partial writes.
How Writers Work in Spring Batch Writers receive a Chunk&amp;lt;O&amp;gt; — a list of all items processed in the current transaction.</description></item><item><title>Your First Migration: MySQL Setup and Running liquibase update</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-first-migration/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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://blog.devops-monk.com/tutorials/spring-boot/first-spring-boot-application/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.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><item><title>Zero-Downtime Deployments: The Expand-Contract Pattern</title><link>https://blog.devops-monk.com/tutorials/liquibase/liquibase-zero-downtime-deployments/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/tutorials/liquibase/liquibase-zero-downtime-deployments/</guid><description>A RENAME COLUMN statement takes milliseconds. But if your application is still running the old code when the rename executes, every query that uses the old column name fails immediately. Zero-downtime schema changes are not about making DDL faster — they are about sequencing changes so that no single step breaks the running application.
The expand-contract pattern is the standard solution. It breaks dangerous migrations into safe, incremental phases that allow old and new application code to coexist with the same database during deployment.</description></item><item><title>About Me</title><link>https://blog.devops-monk.com/about/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/about/</guid><description>Hey, I&amp;rsquo;m Abhay I&amp;rsquo;m a Principal Software Engineer with a passion for automation, cloud infrastructure, and building things that run reliably at scale. I started this blog to document my learning journey and share practical knowledge with the community — the kind of content I wish I had when I was starting out.
What I Work With Frameworks — Spring Boot, Spring Batch Cloud &amp;amp; Infrastructure — AWS, Terraform, and infrastructure as code Containers &amp;amp; Orchestration — Kubernetes, Helm, Docker CI/CD — GitHub Actions, Jenkins, ArgoCD DNS &amp;amp; Networking — PowerDNS, Nginx, self-hosted services Languages — Java, Python, Node.</description></item><item><title>Building a Personal AI Assistant with Claude Agent SDK and Bun</title><link>https://blog.devops-monk.com/2026/04/building-personal-ai-assistant-claude/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/building-personal-ai-assistant-claude/</guid><description>Most AI assistants are chatbots. You ask, they answer, the interaction ends. The interesting shift happening right now is treating AI as an autonomous worker — something that runs on a schedule, produces real artifacts, and delivers results without you being in the loop.
This post walks through building that kind of assistant: a background agent that runs weekly, researches a set of topics relevant to your work, and delivers a structured briefing via Telegram or email.</description></item><item><title>Building a Zero-Cost Stock Market Intelligence Platform</title><link>https://blog.devops-monk.com/2026/04/stock-market-analysis-dashboard/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/stock-market-analysis-dashboard/</guid><description>Most stock screeners cost $30–$200 per month. Bloomberg Terminal costs $24,000 per year. I built something that does a meaningful fraction of what those tools do — analysing 220+ UK and US stocks every hour, scoring them across six dimensions, detecting bearish warning signals, running insider trading checks via SEC EDGAR, and presenting everything in a React PWA — at zero ongoing cost.
The platform is live at share.devops-monk.com. The full source is at github.</description></item><item><title>Claude Code as a Security Scanner: Beyond Pattern Matching</title><link>https://blog.devops-monk.com/2026/04/claude-code-security-scanner/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/claude-code-security-scanner/</guid><description>Tools like ESLint, Semgrep, and Bandit catch what they are programmed to find: known patterns, common injection strings, deprecated API calls. They are fast, reliable, and deterministic. They are also blind to anything that requires understanding what your code is supposed to do.
Claude Code operates differently. It reads code the way a human security researcher would — tracing data flows across files, understanding business logic, and reasoning about what could go wrong given the specific context of your application.</description></item><item><title>Claude Code Hooks, Commands, Skills, and Subagents: The Complete Guide</title><link>https://blog.devops-monk.com/2026/04/claude-code-hooks-subagents-piping/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/claude-code-hooks-subagents-piping/</guid><description>Most teams use Claude Code reactively — they type a prompt, Claude responds, they type another. That is fine, but it leaves significant value on the table. Claude Code has four automation layers that let you turn it from a reactive assistant into an active workflow participant:
Layer What it does When to reach for it Hooks Shell or HTTP calls that fire on lifecycle events &amp;ldquo;This must happen every time, without exception&amp;rdquo; Custom Commands Reusable slash commands for repeatable prompts &amp;ldquo;I type the same prompt repeatedly&amp;rdquo; Skills Context-aware instructions Claude loads automatically &amp;ldquo;Claude should always do X when working on Y&amp;rdquo; Subagents Separate Claude instances for isolated, parallel work &amp;ldquo;This task is noisy and the main session only needs a summary&amp;rdquo; This post covers how to create each one and when to use them.</description></item><item><title>Claude Computer Use for DevOps: When to Use It and When to Use an API</title><link>https://blog.devops-monk.com/2026/04/claude-computer-use-devops-workflows/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/claude-computer-use-devops-workflows/</guid><description>Claude&amp;rsquo;s computer use capability — the ability to see your screen and interact with applications via mouse clicks, keyboard input, and scrolling — is one of the most discussed features in the AI space. It is also one of the most misapplied.
The correct mental model is not &amp;ldquo;Claude can now automate everything on my screen.&amp;rdquo; It is &amp;ldquo;Claude now has a flexible fallback layer for tasks that do not have a structured API integration.</description></item><item><title>Mastering Claude Code CLI: The Complete Guide for DevOps Engineers</title><link>https://blog.devops-monk.com/2026/04/mastering-claude-code-cli/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/mastering-claude-code-cli/</guid><description>If you have been using Claude in a browser tab to help with code, you are leaving most of its capability on the table. Claude Code CLI brings the full power of Claude directly into your terminal — it reads your actual codebase, runs real commands, edits files, commits code, and integrates with every tool in your DevOps stack. This guide covers everything from installation to advanced patterns that most engineers never discover.</description></item><item><title>MCP Servers Worth Installing: For Developers, Testers, and DevOps Teams</title><link>https://blog.devops-monk.com/2026/04/mcp-servers-for-devops-teams/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/mcp-servers-for-devops-teams/</guid><description>The MCP (Model Context Protocol) ecosystem now has over 14,000 servers. Most of them you do not need. Installing too many slows responses, inflates your context window with tool definitions that never get used, and turns debugging into a guessing game across a dozen integrations.
This guide applies a simple filter: only install a server if it replaces a daily copy-paste workflow. It is organised by role — developers, QA testers, and DevOps/platform engineers — because the right stack is different for each.</description></item><item><title>Stop Burning Tokens: A Practical Guide to Claude Code Cost Optimization</title><link>https://blog.devops-monk.com/2026/04/claude-code-token-optimization/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/claude-code-token-optimization/</guid><description>Token usage with Claude Code follows a frustrating pattern: costs are not spread evenly — they cluster around a handful of bad habits. Most developers using Claude Code daily are burning 40–60% more tokens than they need to, simply because of how they phrase prompts, what they put in CLAUDE.md, and which model they reach for by default.
This guide covers five concrete changes that make an immediate difference.
Why Tokens Are Worth Caring About Every message you send in a Claude Code session includes:</description></item><item><title>The Claude Code /loop Command: In-Session Automation Explained</title><link>https://blog.devops-monk.com/2026/04/claude-code-loop-command/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/claude-code-loop-command/</guid><description>The /loop command in Claude Code lets you schedule a prompt to repeat at a regular interval within an active session. It is one of the less-documented features, and it is also one of the most misunderstood — particularly around what it cannot do.
This post explains what /loop actually is, the three scenarios where it genuinely earns its keep, and where cron jobs remain the better choice.
What /loop Does /loop 5m check if the Vite dev server is still running and report any new errors This runs the prompt immediately, then repeats it every 5 minutes for the duration of your session.</description></item><item><title>Writing a CLAUDE.md That Actually Works</title><link>https://blog.devops-monk.com/2026/04/writing-claude-md-that-works/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/writing-claude-md-that-works/</guid><description>Every CLAUDE.md file gets loaded into context on every session. Most teams treat it like documentation — a place to describe the project, list the tech stack, explain what the tests do. That is the wrong mental model and it is why most CLAUDE.md files are both too long and too ineffective.
CLAUDE.md is behavioral programming. Its job is to change how Claude makes decisions, not to describe facts that Claude can read from the codebase itself.</description></item><item><title>You Don't Need a Framework to Build an AI Assistant</title><link>https://blog.devops-monk.com/2026/04/build-ai-assistant-without-framework/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/build-ai-assistant-without-framework/</guid><description>There is a tendency in the AI tooling space to reach for frameworks — LangChain, AutoGen, CrewAI, OpenClaw — the moment you want an AI that does more than answer one question at a time. Most of the time, that is the wrong move. The framework adds complexity, dependencies, and debugging surface area for problems that a few shell scripts and cron jobs solve perfectly well.
Claude Code&amp;rsquo;s headless mode (-p flag) plus a markdown file for personality plus cron scheduling is a complete AI assistant stack.</description></item><item><title>Build Your Own DDNS Platform</title><link>https://blog.devops-monk.com/2026/04/build-your-own-ddns-platform/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.devops-monk.com/2026/04/build-your-own-ddns-platform/</guid><description>If you run a home server — a Raspberry Pi, a NAS, a Kubernetes cluster in your garage — you have probably hit the same annoying wall: your internet provider gives you a different public IP address every few days, and suddenly nobody can reach your server anymore. This post explains how I solved that problem by building ddns.devops-monk.com, a fully self-hosted Dynamic DNS platform. I will walk through the idea from scratch, explain every moving part in plain English, and include full architecture diagrams for those who want the deep technical picture.</description></item><item><title>Getting Started With ChartMuseum</title><link>https://blog.devops-monk.com/2020/09/getting-started-with-chartmuseusm/</link><pubDate>Sat, 05 Sep 2020 02:05:05 +0000</pubDate><guid>https://blog.devops-monk.com/2020/09/getting-started-with-chartmuseusm/</guid><description>When you build custom Helm charts for your organisation, you need somewhere to store and distribute them. Public registries like Artifact Hub are not suitable for internal charts. ChartMuseum is an open-source Helm chart repository server that you can run on your own infrastructure — with support for local storage, AWS S3, GCS, Azure Blob, and more.
How It Fits Into Your Workflow flowchart LR Dev[Developer] -->|helm package| Chart[chart.tgz] Chart -->|curl POST| CM[ChartMuseum Server] CM -->|stores in| Storage[Local / S3 / GCS / Azure] CI[CI Pipeline] -->|helm install| CM Cluster[Kubernetes Cluster] -->|pulls chart| CI ChartMuseum exposes a standard Helm repository API — any Helm client can add it as a repository and install charts from it exactly like any public repo.</description></item><item><title>Getting Started With Helm 3</title><link>https://blog.devops-monk.com/2020/09/getting-started-with-helm3/</link><pubDate>Sat, 05 Sep 2020 02:05:05 +0000</pubDate><guid>https://blog.devops-monk.com/2020/09/getting-started-with-helm3/</guid><description>Helm is the package manager for Kubernetes — the same idea as apt on Ubuntu or npm in Node.js, but for deploying applications to your cluster. Instead of writing and maintaining dozens of raw Kubernetes YAML files per application, you define a chart once, parameterise it with values, and deploy it consistently across every environment.
Note: Helm 4 was released in 2025 with breaking changes. This guide covers Helm 3, which remains widely used and supported.</description></item><item><title>HashiCorp Certified Terraform Associate</title><link>https://blog.devops-monk.com/2020/07/hashicorp-certified-terraform-associate/</link><pubDate>Sun, 19 Jul 2020 02:05:05 +0000</pubDate><guid>https://blog.devops-monk.com/2020/07/hashicorp-certified-terraform-associate/</guid><description>Finally, HashiCorp has announced the general availability of some of their Cloud Certifications like Terraform and Vault (and others to come like Consul).The exam is taken online with a live proctor. Its a mix of multiple-choices and “fill in the blanks” test with 57 questions for 1 hour.
Terraform associate is a foundational level of certification that evaluates your understanding of basic concepts and skills on Terraform OSS and the features exist on Terraform Cloud &amp;amp; Terraform Enterprise packages.</description></item><item><title>Kubernetes practice questions for CKAD exam ?</title><link>https://blog.devops-monk.com/2020/07/kubernetes-practice-CKAD-Exam/</link><pubDate>Sun, 19 Jul 2020 02:05:05 +0000</pubDate><guid>https://blog.devops-monk.com/2020/07/kubernetes-practice-CKAD-Exam/</guid><description>The CKAD (Certified Kubernetes Application Developer) is a performance-based exam from the CNCF/Linux Foundation. Unlike multiple choice tests, you work in a live Kubernetes cluster — you must know your kubectl commands cold and be able to write YAML from memory under time pressure.
Exam details (2024/2025):
Duration: 2 hours Format: Performance-based, hands-on in a live cluster (Kubernetes v1.31+) Pass mark: 66% Cost: $395 (includes one free retake) Current Exam Domains Domain Weight Application Design and Build 20% Application Deployment 20% Application Environment, Configuration and Security 25% Application Observability and Maintenance 15% Services and Networking 20% The Environment, Configuration and Security domain carries the most weight — ConfigMaps, Secrets, SecurityContexts, and resource limits are tested heavily.</description></item><item><title>Contact</title><link>https://blog.devops-monk.com/contact/</link><pubDate>Sun, 12 Jul 2020 13:06:31 +0000</pubDate><guid>https://blog.devops-monk.com/contact/</guid><description>You can reach me at:
Email: abhaypratap3536@gmail.com
GitHub: https://github.com/devops-monk</description></item></channel></rss>