Posts

Showing posts with the label Microservices

A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android

Image
I’m excited to share a set of mobile apps I’ve recently completed and published on both the Google Play Store and the Apple App Store. These apps are designed with a simple goal in mind: to make meaningful, structured content more accessible, whether you’re studying theology or improving your English vocabulary. 📱 Now Available on Both Platforms All apps are live and available for download: Google Play Developer Page: https://play.google.com/store/apps/dev?id=5835943159853189043 Apple App Store Developer Page: https://apps.apple.com/ca/developer/q-z-l-corp/id1888794100 📖 Theology & Confession Study Apps For those interested in Reformed theology and classical Christian teachings, I’ve developed a series of apps that present foundational texts in a clean, focused reading format: The Belgic Confession Canons of Dort Heidelberg Catechism Westminster Shorter Catechism Each app is designed to provide a distraction-free experience, making it easier to read, reflect, and revisit these im...

Your Spring Boot Transaction Doesn't Make Event Publishing Reliable

Image
Your Spring Boot Transaction Doesn't Make Event Publishing Reliable A request marks an order as paid and then publishes an OrderPaid event. Both calls may succeed in a normal test. But the database and a message broker are separate systems: the database can commit while the broker is unavailable, or the broker can accept a message just before the database transaction rolls back. A Spring @Transactional method does not, by itself, make those two systems one atomic transaction. This gap is a common source of events that are missing, duplicated, or inconsistent with the business record. A transactional outbox narrows the gap by making the business change and the intent to publish an event part of the same database transaction. Write the event intent with the business change Instead of calling the broker inside the request's database transaction, insert a durable outbox row alongside the order update: BEGIN; UPDATE orders SET status = 'PAID' WHERE id = :order_id; INSE...

Spring Boot Observability: Propagating Trace Context Across Async Threads

Image
Spring Boot @Async: Fixing Distributed Tracing Context Propagation Across Threads Have you ever noticed that distributed traces suddenly disappear when your Spring Boot application executes an asynchronous task? A request enters your application, calls a service, and then schedules background work using @Async . The initial request appears correctly in your tracing dashboard, but the background execution is missing—or worse, it appears as an entirely unrelated trace. The problem may not be your tracing backend. It may be that the request's observation context was not propagated to the worker thread. Distributed tracing relies on context: identifiers and other state that connect operations across services and execution boundaries. OpenTelemetry provides mechanisms to propagate this context between services, usually without requiring application code to copy identifiers manually. However, there is another boundary within the same application that is easy to overlook: switching thre...

How to Optimize a Slow JPA Query Processing 40K+ IDs (Real Performance Tuning Guide)

Image
How to Optimize a Slow JPA Query Processing 40K+ IDs (Real Performance Tuning Guide) Handling large datasets efficiently is one of the most common challenges in backend engineering. In this post, I’ll walk through how a query processing 40,000+ IDs went from ~24 seconds down to a few seconds using practical optimization techniques. All domain-specific details have been intentionally removed to focus purely on reusable engineering patterns. 📉 The Problem A service fetches data using a JPA query with a large IN clause and joins between tables: SELECT new SomeDto( A.primaryId, A.field1, A.field2, B.field3, C.metric1, C.metric2 ) FROM EntityA A LEFT JOIN A.relatedEntity B LEFT JOIN EntityC C ON C.refId = A.primaryId WHERE A.primaryId IN :ids Even with chunking (~900 IDs per query), total execution time was around 24 seconds . 🚨 Root Causes Unoptimized JOIN conditions Missing database indexes Sequential query execution ORM (JPA) over...

How We Made a Spring Boot Pagination Query 36× Faster (1.8 Minutes → 3 Seconds)

Image
How We Optimized a Spring Boot Pagination Query by 36× (From 1.8 Minutes to 3 Seconds) In high-traffic backend systems, slow database queries are often the hidden bottleneck that quietly kills performance. In our case, a paginated query powering a reporting API was taking 1.8 minutes to respond under load. After optimization, we reduced it to ~3 seconds . 36× faster ~97.2% less execution time ~3500% performance improvement Here’s how we did it — and the key lessons you can apply immediately. 🧱 The Problem: Slow Pagination Query We had a Spring Data JPA query backing a paginated API. It involved multiple filters, joins, and was returning a large dataset. Original issues: Full entity loading Heavy object graph hydration Unnecessary columns fetched Inefficient filtering logic Expensive pagination count The database schema (simplified and anonymized) looked like this: TABLE txn_line ( line_id BIGINT, order_id BIGINT, product_code ...

Spring Boot 4 Upgrade Pitfalls Guide: Java 21, Jakarta, Jackson 3, and Copilot Automation

Image
Spring Boot 4 Upgrade Pitfalls Guide: Java 21, Jakarta, Jackson 3, and Copilot Automation Spring Boot 4 introduces major ecosystem changes and many teams are planning to upgrade their applications to Java 21 and Spring Boot 4.0.x . However, real-world upgrades often reveal multiple compatibility issues such as: Jakarta package migration breaking compilation Jackson 3 JSON behavior changes Spring Cloud Gateway configuration updates Test framework migration issues Micrometer / Observability runtime errors This article summarizes a production-ready upgrade strategy and demonstrates how to use GitHub Copilot Agent to automate much of the migration work. Table of Contents Why Upgrade to Spring Boot 4 Recommended Upgrade Strategy Using GitHub Copilot for Automated Migration Java 21 Upgrade Spring Boot 4 Upgrade Jakarta Migration Jackson 3 Changes Spring Cloud Gateway Changes Common Upgrade Pitfalls Upgrade Verification Checklist Why Upgrade to Spring...

Spring Boot 4 Upgrade Guide (Using GitHub Copilot Agent)

Image
Spring Boot 4 Upgrade Guide (Using GitHub Copilot Agent) Upgrading to Java 21 and Spring Boot 4.0.3 is not just a dependency bump — it requires a structured migration strategy that preserves behavior, maintains stability, and ensures compatibility across the ecosystem. This guide outlines a production-ready upgrade framework designed for enterprise Spring services. It integrates phased migration, official Spring guidance, and GitHub Copilot Agent automation to produce clean, reviewable upgrade pull requests. Upgrade Objectives Upgrade to Java 21 Upgrade to Spring Boot 4.0.3 Align with compatible Spring Cloud version Preserve business logic Maintain Maven build Ensure compilation, tests, and successful startup Upgrade Principles Preserve existing behaviour Do NOT remove business logic Make minimal but correct changes Follow official Spring migration guidance Upgrade incrementally with validation after each phase Official Migration Reference All...

一次 403 错误的排查实录:Spring Cloud Gateway、WebFlux、PingFederate 与 CORS

Image
一次 403 错误的排查实录:Spring Cloud Gateway、WebFlux、PingFederate 与 CORS 一次真实的线上排错记录:一个看似 OAuth / JWT / Spring Security 的 403,最终却是 CORS 问题。 背景 我们在一次系统集成过程中,遇到了一个非常令人困惑的问题,涉及以下组件: Spring Cloud Gateway Spring Boot WebFlux 后端服务 PingFederate(OIDC / OAuth2) 本地运行的 React 前端( http://localhost:4200 ) 整体请求流程如下: 用户通过 PingFederate 登录(SSO) 前端获取 access token 前端发起 POST 请求 → Gateway → 后端服务 在以下场景中,一切都运行正常: 使用 Postman 直接调用后端 绕过 Gateway,直接调用后端 所有组件都在本地运行 但一旦请求通过 部署在 OpenShift 中的 Spring Cloud Gateway ,所有 POST 请求都会失败,并返回: 403 Forbidden 为什么这个问题如此迷惑 完全相同的 access token,在 Postman 中可以正常使用 GET 请求成功,POST 请求失败 系统中没有配置任何 role 或 scope 校验 不同环境下 401 / 403 的表现不一致 JWT 的 issuer、audience、签名全部正确 所有线索一开始都指向 OAuth 或 PingFederate 的配置问题——但这个判断是错误的。 关键线索:问题是如何被定位出来的 1. Postman 成功,浏览器失败 同一个请求,在不同客户端表现完全不同: Postman → 成功 浏览器(经 Gateway)→ 403 这一步非常关键,它强烈暗示: 问题并不在 token 本身 。 2. 在 Postman 中加上 Origin 头,请求立刻失败 我们在 Postman 中手动加入以下 header: O...

Debugging 403 Errors with Spring Cloud Gateway, WebFlux, PingFederate, and CORS

Image
Debugging 403 Errors with Spring Cloud Gateway, WebFlux, PingFederate, and CORS A real-world postmortem on a confusing 403 that turned out not to be OAuth, JWT, or Spring Security — but CORS. Background We recently ran into a frustrating issue while integrating: Spring Cloud Gateway Spring Boot WebFlux backend PingFederate (OIDC / OAuth2) A React frontend running locally ( http://localhost:4200 ) The request flow looked like this: User logs in via PingFederate (SSO) Frontend receives an access token Frontend sends POST requests → Gateway → Backend service Everything worked perfectly when: Calling the backend directly via Postman Calling the backend directly without the gateway Running everything locally But once the request went through Spring Cloud Gateway in OpenShift , all POST requests failed with: 403 Forbidden Why This Was Confusing The same access token worked in Postman GET requests succeeded, POST requests fa...