惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
P
Palo Alto Networks Blog
P
Proofpoint News Feed
AI
AI
Help Net Security
Help Net Security
S
Securelist
T
Troy Hunt's Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
Cisco Blogs
Scott Helme
Scott Helme
Hacker News - Newest:
Hacker News - Newest: "LLM"
Vercel News
Vercel News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
GbyAI
GbyAI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Proofpoint News Feed
S
Security Affairs
Cisco Talos Blog
Cisco Talos Blog
AWS News Blog
AWS News Blog
T
Tenable Blog
H
Help Net Security
NISL@THU
NISL@THU
F
Fortinet All Blogs
博客园_首页
G
GRAHAM CLULEY
L
LINUX DO - 最新话题
P
Privacy International News Feed
G
Google Developers Blog
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
The Register - Security
The Register - Security
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
T
Tor Project blog
P
Privacy & Cybersecurity Law Blog
量子位
C
Cyber Attacks, Cyber Crime and Cyber Security
Forbes - Security
Forbes - Security
S
Secure Thoughts
Simon Willison's Weblog
Simon Willison's Weblog
D
Docker
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
L
Lohrmann on Cybersecurity
T
Tailwind CSS Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Spring Boot in Real Production: What My Lakaut Codebase Taught Me That the Official Docs Leave Out
Juan Torchia · 2026-05-10 · via DEV Community

Spring Boot in Real Production: What My Lakaut Codebase Taught Me That the Official Docs Leave Out

A datasource pool is basically like the ticket booth at a sold-out stadium show. When the crowd is light, everything works perfectly — people show up, grab their spot, walk in. But when the stadium fills up all at once and 300 people are trying to get through the gate simultaneously, the whole system collapses. Not because it's broken. Because it was never designed for that peak moment. And the official Spring Boot documentation shows you the empty ticket booth. It never shows you the concert.

That's exactly what I ran into at Lakaut Hub — the core system for Lakaut AC, the digital certification authority where I work as architect. Real production. Real load. Logs that don't lie.

My thesis is an uncomfortable one: Spring Boot is documented for an idealized environment that doesn't exist on PaaS platforms like Railway. The defaults are built for local development with infinite resources, and in production with real JVM tuning and PostgreSQL connections under load, those defaults will burn you. I know because I have the logs.


The spring.jpa.open-in-view Problem Nobody Explains Seriously

When I started on Lakaut Hub, the app booted, worked fine, and somewhere in the logs there was a warning I ignored for weeks:

WARN  o.s.b.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration
      - spring.jpa.open-in-view is enabled by default.
        Therefore, database queries may be performed during view rendering.
        Explicitly configure spring.jpa.open-in-view to disable this warning

Enter fullscreen mode Exit fullscreen mode

open-in-view=true is the default. What that means in practice: the Hibernate session stays open for the entire lifecycle of the HTTP request, from the moment the request comes in until the response finishes rendering. The docs mention it. What they don't tell you is what it costs you in terms of datasource pool connections held under load.

I measured this directly in Lakaut Hub with Actuator enabled:

# application.yml — before the fix
spring:
  jpa:
    open-in-view: true  # silent default that eats your connections

Enter fullscreen mode Exit fullscreen mode

With an endpoint making multiple JPA queries, every request was holding a connection from the pool from the moment it entered until the JSON response went out — including any business logic, validations, and serializations that didn't need the database at all. With 50 concurrent requests during peak moments at Lakaut Hub, we started seeing connection pool acquisition timeouts. It wasn't a bug in the app. It was the default.

The fix is one line, but the understanding is what matters:

# application.yml — after the fix
spring:
  jpa:
    open-in-view: false  # release the connection back to the pool as soon as you're done with the DB
  datasource:
    hikari:
      maximum-pool-size: 10       # for Railway: don't exceed what your plan supports
      minimum-idle: 5             # don't start from zero on every spike
      connection-timeout: 20000   # 20s before throwing HikariTimeoutException
      idle-timeout: 300000        # release idle connections after 5 min
      max-lifetime: 1200000       # 20 min max lifetime per connection

Enter fullscreen mode Exit fullscreen mode

The p95 response time on Lakaut Hub's most critical endpoint dropped noticeably after this change. I don't have a magic number to show you because load conditions vary, but the trend in the Actuator logs was clear and immediate. If you're running JPA on Railway, turn off open-in-view from day one.


JVM Tuning on Railway: The Defaults Kill You in Containers

This is the most dangerous gotcha, and the one that took me the longest to understand.

Railway runs the JVM inside a container. The JVM, by default, reads resources from the physical host — not the container. By 2026 this is mostly addressed with container-aware flags, but the problem is more subtle: Spring Boot doesn't explicitly tell you which flags to pass to the JVM, and the default garbage collector isn't tuned for a container with 512MB or 1GB of RAM.

When I first deployed Lakaut Hub on Railway, the startup time was erratic:

# Railway log — startup without tuning
Started LakautHubApplication in 18.432 seconds (process running for 19.1)

Enter fullscreen mode Exit fullscreen mode

Eighteen seconds. For a service that needs to be available and responding to digital certificate requests. Unacceptable.

The problem was twofold: automatic heap sizing that didn't respect container limits, and the default GC (G1GC) configured for large heaps. I adjusted the Dockerfile and Railway's JAVA_OPTS:

# Dockerfile — Lakaut Hub
FROM eclipse-temurin:21-jre-alpine

# Copy the jar from the build stage
COPY --from=builder /app/target/lakaut-hub.jar app.jar

# Explicit flags for containers: tell the JVM to read container limits
ENTRYPOINT ["java", \
  "-XX:+UseContainerSupport", \
  "-XX:MaxRAMPercentage=75.0", \
  "-XX:InitialRAMPercentage=50.0", \
  "-XX:+UseZGC", \
  "-XX:+ZGenerational", \
  "-Dspring.profiles.active=production", \
  "-jar", "app.jar"]

Enter fullscreen mode Exit fullscreen mode

Why ZGC and not G1GC: in a container with limited memory, G1GC pauses become unpredictable under load. ZGC with generational mode (available since Java 21) has sub-millisecond pauses and performs better in environments where heap is bounded. That's not theory — I measured it on Railway with startup logs:

# Railway log — after tuning
Started LakautHubApplication in 6.891 seconds (process running for 7.4)

Enter fullscreen mode Exit fullscreen mode

From 18 seconds to 7. Without touching a single line of business code. Just JVM flags and a GC switch.

The official Spring Boot docs don't cover this. There's a section on "Optimizing Startup Time" that mentions lazy initialization, but JVM tuning for containers on specific PaaS platforms isn't there. You're on your own, with the logs and trial and error.


The @Transactional Proxy Gotcha That Cost Me an Incident

This is the one I'm most embarrassed to document, but also the most useful.

Spring Boot implements @Transactional through AOP proxies. The basic rule is: if you call a @Transactional method from within the same class, the proxy gets bypassed and the transaction doesn't exist. The docs say so. What they don't tell you is which real-world scenarios make this explode silently.

In Lakaut Hub we have a digital certificate issuance service. Simplified, it looked like this:

@Service
public class CertificateService {

    // This method DOES have a transaction — called from outside
    @Transactional
    public void issueCertificate(IssuanceRequest request) {
        validateRequest(request);
        persistCertificate(request);
        // SILENT ERROR: this calls a method on the same class
        notifyIssuance(request);
    }

    // This method also has @Transactional, but it will NEVER participate
    // in a separate transaction because Spring can't intercept it —
    // it's called directly (this.notifyIssuance), not through the proxy
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void notifyIssuance(IssuanceRequest request) {
        // We wanted this to run in its own transaction
        // so a failure here wouldn't roll back the certificate issuance.
        // It never did. And there was no error — it just ran in the same tx.
        logNotificationService.register(request.getCertificateId());
    }
}

Enter fullscreen mode Exit fullscreen mode

The result: when notifyIssuance failed, it rolled back the entire issueCertificate transaction. The certificate was lost. The incident lasted two hours diagnosing why there were issuances showing up in the business logs but not in the database.

The fix requires breaking the self-invocation. There are several approaches — the cleanest in our case was separating the service:

@Service
public class CertificateService {

    private final NotificationService notificationService; // separate service

    @Transactional
    public void issueCertificate(IssuanceRequest request) {
        validateRequest(request);
        persistCertificate(request);
        // Now it goes through the Spring proxy — separate transaction guaranteed
        notificationService.notifyIssuance(request);
    }
}

@Service
public class NotificationService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void notifyIssuance(IssuanceRequest request) {
        // This one actually runs in its own transaction
        logNotificationService.register(request.getCertificateId());
    }
}

Enter fullscreen mode Exit fullscreen mode

The interesting part is that this gotcha is decades old. It's in the documentation, on Stack Overflow, in Spring books. And I still hit it in production in 2026, in a codebase I wrote myself. Because in the domain context, the separation of responsibilities wasn't obvious until the incident made it obvious.

Debugging concurrency in production is similar to what I described in the post about mutex deadlock in Rust and diagnostic patterns in a real codebase — the lesson is the same: concurrency and transaction problems are silent until they aren't.


Application Context Under Restart and the Railway Gap

Last gotcha, and the most PaaS-specific one.

Railway does zero-downtime deployments using rolling restarts. When you push a new version, there's a window where the old instance and the new one run simultaneously. With Spring Boot and state in the ApplicationContext, this can produce weird conditions if you have stateful beans or in-memory caches that initialize at startup.

In Lakaut Hub we have a CRL (Certificate Revocation List) cache that initializes at startup from the database. During the rolling restart, the new instance would boot with an empty cache and start serving requests before the cache was warm. The first 30–60 seconds of a new instance had noticeably higher latencies.

The fix was implementing a real health check that Railway uses to determine when the instance is actually ready:

@Component
public class CrlCacheHealthIndicator implements HealthIndicator {

    private final CrlCacheService crlCacheService;

    @Override
    public Health health() {
        // Railway won't send traffic until this returns UP
        if (!crlCacheService.isWarmedUp()) {
            return Health.down()
                .withDetail("reason", "CRL cache still loading")
                .withDetail("entriesLoaded", crlCacheService.getLoadedCount())
                .build();
        }
        return Health.up()
            .withDetail("crlEntries", crlCacheService.getLoadedCount())
            .build();
    }
}

Enter fullscreen mode Exit fullscreen mode

# application.yml — health check configuration for Railway
management:
  endpoints:
    web:
      exposure:
        include: health, metrics, info
  endpoint:
    health:
      show-details: always
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

Enter fullscreen mode Exit fullscreen mode

And in railway.toml:

[deploy]
healthcheckPath = "/actuator/health/readiness"
healthcheckTimeout = 60  # seconds Railway waits before considering the deploy failed

Enter fullscreen mode Exit fullscreen mode

Without this, Railway assumes the instance is ready as soon as the port is open. And Spring Boot's port is open before the ApplicationContext finishes initializing completely. Railway's docs don't mention this for Java. Spring Boot's docs don't talk about Railway. You're in the gap.

That kind of gap between what a provider promises and what happens in real production reminds me of the analysis I did on supply chain attacks in npm where the scanner doesn't see everything — the official promise and reality always have a distance that only closes with your own evidence.


Common Mistakes That Aren't in the Official Docs

1. Trusting spring.datasource.url without ?sslmode=require on Railway PostgreSQL
Railway PostgreSQL requires SSL. Without the explicit parameter, some versions of the JDBC driver connect without SSL and the connection fails silently or with cryptic messages. Always: ?sslmode=require&sslrootcert=system.

2. Using spring.jpa.hibernate.ddl-auto=update in production
The docs advise against it. People use it anyway. In Lakaut Hub I found it in a PR branch that almost reached main. update can lose data on non-trivial migrations. In production: validate + Flyway or Liquibase, always.

3. Ignoring startup warnings
open-in-view, lazy initialization disabled without justification, beans with duplicate names — Spring Boot logs these as WARN and people ignore them. I ignored them. It cost me weeks of diagnosis that could have been avoided with ten minutes of reading the first deploy's logs.

4. Not separating profiles by environment
A single application.properties for everything. Lakaut Hub started that way. The problem is that dev values (small heap, minimal pool, verbose logging) end up in production by default. Separating into application-production.yml with the correct values is mandatory from day one, not after the problem is already there.


FAQ — Spring Boot in Real Production

What datasource pool size do you recommend for Railway with PostgreSQL?
It depends on your Railway plan and the connection limits of the Postgres server. As a starting point: maximum-pool-size between 5 and 10, minimum-idle at half that. HikariCP's formula suggests (cores * 2) + spindle_disks, but on Railway you need to measure what connection limit your plan has and not exceed it across all instances.

ZGC or G1GC for Spring Boot in containers?
For Java 21+ in containers with bounded heap (512MB–2GB), ZGC Generational is my current choice. G1GC works well with large heaps (4GB+). With limited memory on Railway, G1GC pauses become unpredictable. I measured the switch in Lakaut Hub and the difference was clear in startup time and p99 latency.

How do you diagnose a connection pool problem in production without direct database access?
Spring Boot Actuator with the /actuator/metrics/hikaricp.connections endpoint gives you real-time pool state: active, idle, pending, timeouts. If hikaricp.connections.pending climbs, the pool is saturated. If hikaricp.connections.timeout has any non-zero values, you've already had real timeouts.

@Transactional at the Controller layer or only in Service?
Only in Service. Never in Controller. The Controller shouldn't know anything about transactions — mixing concerns there breaks layer separation and makes it harder to test business logic in isolation. At Lakaut Hub we have this as a code review checklist rule.

What's the difference between liveness and readiness in Spring Boot Actuator?
liveness says whether the app is alive (if it fails, Railway/Kubernetes restarts it). readiness says whether it's ready to receive traffic (if it fails, Railway pulls traffic but doesn't restart it). For cache warmup and the startup gap, readiness is the one you care about. Configuring only a generic health without separating these two states means you're throwing away half the value of health checking.

Is Spring Boot worth it for small projects, or is it overkill?
Depends on the context. For Lakaut Hub, where the domain is complex (PKI, X.509 certificates, CRLs, TSA), the Spring Security, Spring Data JPA ecosystem and Bouncy Castle integration justify the overhead. For a simple CRUD with three endpoints, Quarkus or Micronaut will probably start faster and use less memory. The question isn't "is Spring Boot good?" but "what does this specific problem actually need?"


The Docs Are the Starting Point, Not the Destination

Three years running Spring Boot in real production at Lakaut AC left me with one conviction: the official documentation is an onboarding guide, not an operations manual. It's written to get your app running. Not to survive a sold-out stadium show.

The four gotchas I documented here — open-in-view and the pool under load, JVM tuning for Railway containers, @Transactional proxies and self-invocation, and the readiness gap in rolling restarts — aren't bugs in Spring Boot. They're design decisions that make sense in the context they were made in. The problem is that context isn't real production on a PaaS with limited resources.

What I don't buy from the Spring ecosystem in 2026 is the tendency to hide complexity under defaults that look magical. open-in-view=true by default is a design choice made so tutorials work without extra configuration. In real production, that default has a price tag. The log disclaimer is useful but insufficient.

What I do accept: when Spring Boot is properly configured and genuinely understood, it's a solid stack for complex domains. Lakaut Hub runs on Railway with JVM 21, PostgreSQL, and the gotchas documented here are resolved. The system issues digital certificates in production every single day. The docs didn't get me there — the logs did.

If you're running Java in production and you've hit gotchas that aren't here, I want to know about them. I'm building out the Java category on this blog from evidence, not from tutorials. There's a lot more to document.


If production diagnosis using real logs is useful to you, I also documented the real guardrails analysis for autonomous agents after a concrete incident — the evidence-first approach applies equally to complex distributed systems.


This article was originally published on juanchi.dev