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

推荐订阅源

Forbes - Security
Forbes - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Palo Alto Networks Blog
Martin Fowler
Martin Fowler
T
Threatpost
D
Docker
S
Schneier on Security
M
MIT News - Artificial intelligence
G
Google Developers Blog
L
LINUX DO - 热门话题
J
Java Code Geeks
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
V
Vulnerabilities – Threatpost
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
人人都是产品经理
人人都是产品经理
Spread Privacy
Spread Privacy
T
Tailwind CSS Blog
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
C
CERT Recently Published Vulnerability Notes
The GitHub Blog
The GitHub Blog
Simon Willison's Weblog
Simon Willison's Weblog
NISL@THU
NISL@THU
MongoDB | Blog
MongoDB | Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Heimdal Security Blog
Recorded Future
Recorded Future
云风的 BLOG
云风的 BLOG
SecWiki News
SecWiki News
P
Privacy International News Feed
P
Proofpoint News Feed
O
OpenAI News
B
Blog
腾讯CDC
F
Full Disclosure
Apple Machine Learning Research
Apple Machine Learning Research
T
Tor Project blog
H
Hacker News: Front Page
Project Zero
Project Zero
Hugging Face - Blog
Hugging Face - Blog
C
Cisco Blogs
S
Security Affairs

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 en producción real: lo que mi codebase de Lakaut me enseñó que la documentación oficial omite
Juan Torchia · 2026-05-10 · via DEV Community

Spring Boot en producción real: lo que mi codebase de Lakaut me enseñó que la documentación oficial omite

Un datasource pool es básicamente como la boletería de un recital de Soda Stereo. Cuando hay poca gente, funciona perfecto — cada uno llega, saca su lugar, entra. Pero cuando el estadio se llena de golpe y hay 300 personas querando entrar a la vez, el sistema colapsa. No porque esté roto. Porque nunca fue diseñado para ese momento de pico. Y la documentación oficial de Spring Boot te muestra la boletería vacía. Nunca te muestra el recital.

Eso es exactamente lo que encontré en Lakaut Hub — el sistema core de Lakaut AC, la autoridad de certificación digital donde trabajo como arquitecto. Producción real. Carga real. Logs que no mienten.

Mi tesis es incómoda: Spring Boot está documentado para un entorno idealizado que no existe en plataformas PaaS como Railway. Los defaults están pensados para desarrollo local con recursos infinitos, y en producción con JVM tuning real y conexiones PostgreSQL bajo carga, esos defaults te van a quemar. Lo sé porque tengo los logs.


El problema con spring.jpa.open-in-view que nadie te explica en serio

Cuando arrancé con Lakaut Hub, la app arrancaba, funcionaba, y en el log había una advertencia que ignoré durante semanas:

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 es el default. Lo que eso significa en la práctica: la sesión de Hibernate queda abierta durante todo el ciclo de vida del request HTTP, desde que entra el pedido hasta que se termina de renderizar la respuesta. La doc lo menciona. Lo que no te dice es cuánto te cuesta eso en términos de conexiones de datasource pool retenidas bajo carga.

Medí esto directamente en Lakaut Hub con Actuator habilitado:

# application.yml — antes del fix
spring:
  jpa:
    open-in-view: true  # default silencioso que te come conexiones

Enter fullscreen mode Exit fullscreen mode

Con un endpoint que hacía múltiples consultas JPA, cada request retenía una conexión del pool desde que entraba hasta que salía la respuesta JSON — incluyendo cualquier lógica de negocio, validaciones y serializaciones que no necesitaban la base de datos para nada. Con 50 requests concurrentes en momentos de pico en Lakaut Hub, empezamos a ver timeouts de adquisición de conexión del pool. No era un bug de la app. Era el default.

El fix es una línea, pero el entendimiento es lo que importa:

# application.yml — después del fix
spring:
  jpa:
    open-in-view: false  # liberás la conexión al pool apenas terminás con la DB
  datasource:
    hikari:
      maximum-pool-size: 10       # para Railway: no sobrepasar lo que el plan soporta
      minimum-idle: 5             # no arrancar desde cero en cada pico
      connection-timeout: 20000   # 20s antes de tirar HikariTimeoutException
      idle-timeout: 300000        # liberar conexiones ociosas a los 5 min
      max-lifetime: 1200000       # 20 min máximo de vida por conexión

Enter fullscreen mode Exit fullscreen mode

El tiempo de respuesta p95 del endpoint más crítico de Lakaut Hub bajó notoriamente después de este cambio. No tengo un número mágico para mostrarte porque las condiciones de carga varían, pero la tendencia en los logs de Actuator fue clara e inmediata. Si trabajan con JPA en Railway, apaguen open-in-view desde el día uno.


JVM tuning en Railway: los defaults te matan en contenedores

Acá está el gotcha más peligroso y el que más tiempo me llevó entender.

Railway corre la JVM dentro de un contenedor. La JVM, por default, lee los recursos del host físico, no del contenedor. En 2026 esto está mayormente resuelto con las container-aware flags, pero el problema es más sutil: Spring Boot no te dice explícitamente qué flags pasarle a la JVM, y los defaults del garbage collector no están pensados para un contenedor con 512MB o 1GB de RAM.

Cuando desplegué Lakaut Hub por primera vez en Railway, el startup time era errático:

# Log de Railway — startup sin tuning
Started LakautHubApplication in 18.432 seconds (process running for 19.1)

Enter fullscreen mode Exit fullscreen mode

Dieciocho segundos. Para un servicio que tiene que estar disponible y responder certificaciones digitales. Inaceptable.

El problema era doble: heap sizing automático que no respetaba los límites del contenedor, y el GC default (G1GC) con configuración pensada para heaps grandes. Ajusté el Dockerfile y el JAVA_OPTS de Railway:

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

# Copiamos el jar del stage de build
COPY --from=builder /app/target/lakaut-hub.jar app.jar

# Flags explícitas para contenedor: le decimos a la JVM que lea los límites del contenedor
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

Por qué ZGC y no G1GC: en un contenedor con memoria limitada, las pausas de G1GC se vuelven impredecibles bajo carga. ZGC con generational mode (disponible desde Java 21) tiene pausas sub-milisegundo y funciona mejor en ambientes donde el heap está acotado. No es teoría — lo medí en Railway con logs de startup:

# Log de Railway — después del tuning
Started LakautHubApplication in 6.891 seconds (process running for 7.4)

Enter fullscreen mode Exit fullscreen mode

De 18 segundos a 7. Sin tocar una línea de código de negocio. Solo flags de JVM y un cambio de GC.

La documentación oficial de Spring Boot no habla de esto. Hay una sección de "Optimizing Startup Time" que menciona lazy initialization, pero el tuning de JVM para contenedores en PaaS específicos no está. Estás solo, con los logs y el trial and error.


El gotcha de @Transactional con proxies que me costó un incidente

Este es el que más vergüenza da documentar, pero también el más útil.

Spring Boot implementa @Transactional mediante proxies de AOP. La regla básica es que si llamás un método @Transactional desde dentro de la misma clase, el proxy se bypasea y la transacción no existe. Lo dice la documentación. Lo que no dice es en qué escenarios reales esto explota silenciosamente.

En Lakaut Hub tenemos un servicio de emisión de certificados digitales. Simplificado, se veía así:

@Service
public class CertificadoService {

    // Este método SÍ tiene transacción — lo llaman desde afuera
    @Transactional
    public void emitirCertificado(EmisionRequest request) {
        validarRequest(request);
        persistirCertificado(request);
        // ERROR SILENCIOSO: esto llama a un método de la misma clase
        notificarEmision(request);
    }

    // Este método también tiene @Transactional, pero NUNCA va a participar
    // en una transacción separada porque Spring no puede interceptarlo —
    // se llama directamente (this.notificarEmision), no a través del proxy
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void notificarEmision(EmisionRequest request) {
        // Queríamos que esto corriera en su propia transacción
        // para que un fallo acá no rollbackeara la emisión del certificado.
        // No pasaba. Nunca. Y no había error — simplemente corría en la misma tx.
        logNotificacionService.registrar(request.getCertificadoId());
    }
}

Enter fullscreen mode Exit fullscreen mode

El resultado: cuando notificarEmision fallaba, rollbackeaba toda la transacción de emitirCertificado. El certificado se perdía. El incidente duró dos horas diagnosticando por qué había emisiones que aparecían en los logs de negocio pero no en la base de datos.

El fix requiere romper la auto-invocación. Hay varias formas — la más limpia en nuestro caso fue separar el servicio:

@Service
public class CertificadoService {

    private final NotificacionService notificacionService; // servicio separado

    @Transactional
    public void emitirCertificado(EmisionRequest request) {
        validarRequest(request);
        persistirCertificado(request);
        // Ahora sí pasa por el proxy de Spring — transacción separada garantizada
        notificacionService.notificarEmision(request);
    }
}

@Service
public class NotificacionService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void notificarEmision(EmisionRequest request) {
        // Esta sí corre en su propia transacción
        logNotificacionService.registrar(request.getCertificadoId());
    }
}

Enter fullscreen mode Exit fullscreen mode

Lo interesante es que este gotcha tiene décadas. Está en la documentación, en Stack Overflow, en libros de Spring. Y aun así me lo encontré en producción en 2026, en un codebase que escribí yo mismo. Porque en el contexto del dominio, la separación de responsabilidades no era obvia hasta que el incidente la hizo obvia.

El debugging de concurrencia en producción es similar a lo que describí en el post sobre mutex deadlock en Rust y patrones de diagnóstico en codebase real — la lección es la misma: los problemas de concurrencia y transacciones son silenciosos hasta que no lo son.


El contexto de aplicación bajo restart y el gap con Railway

Último gotcha, y el más específico a PaaS.

Railway hace deployments sin downtime usando rolling restarts. Cuando subís una nueva versión, hay un período donde la instancia vieja y la nueva corren simultáneamente. Con Spring Boot y estado en el ApplicationContext, esto puede generar condiciones raras si tenés beans con estado (stateful beans) o caches en memoria que se inicializan al arrancar.

En Lakaut Hub tenemos un cache de CRLs (Certificate Revocation Lists) que se inicializa al startup desde la base de datos. Durante el rolling restart, la instancia nueva arrancaba con el cache vacío y empezaba a servir requests antes de que el cache estuviera caliente. Los primeros 30-60 segundos de una instancia nueva tenían latencias notoriamente más altas.

El fix fue implementar un health check real que Railway usa para determinar cuándo la instancia está lista:

@Component
public class CrlCacheHealthIndicator implements HealthIndicator {

    private final CrlCacheService crlCacheService;

    @Override
    public Health health() {
        // Railway no manda tráfico hasta que esto devuelva UP
        if (!crlCacheService.isWarmedUp()) {
            return Health.down()
                .withDetail("razon", "CRL cache todavia cargando")
                .withDetail("entriesLoaded", crlCacheService.getLoadedCount())
                .build();
        }
        return Health.up()
            .withDetail("crlEntries", crlCacheService.getLoadedCount())
            .build();
    }
}

Enter fullscreen mode Exit fullscreen mode

# application.yml — configuración de health checks para 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

Y en el railway.toml:

[deploy]
healthcheckPath = "/actuator/health/readiness"
healthcheckTimeout = 60  # segundos que Railway espera antes de considerar el deploy fallido

Enter fullscreen mode Exit fullscreen mode

Sin esto, Railway asume que la instancia está lista apenas el puerto está abierto. Y el puerto de Spring Boot está abierto antes de que el ApplicationContext termine de inicializarse completamente. La doc de Railway no menciona esto para Java. La doc de Spring Boot no habla de Railway. Estás en el gap.

Este tipo de gap entre lo que un proveedor promete y lo que pasa en producción real me recuerda al análisis que hice sobre supply chain attacks en npm donde el scanner no ve todo — la promesa oficial y la realidad tienen siempre una distancia que solo cierra con evidencia propia.


Errores comunes que no vienen en la doc oficial

1. Confiar en spring.datasource.url sin ?sslmode=require en Railway PostgreSQL
Railway PostgreSQL requiere SSL. Sin el parámetro explícito, algunas versiones del driver JDBC conectan sin SSL y la conexión falla silenciosamente o con mensajes crípticos. Siempre: ?sslmode=require&sslrootcert=system.

2. Usar spring.jpa.hibernate.ddl-auto=update en producción
La doc lo desaconseja. La gente igual lo usa. En Lakaut Hub lo encontré en un branch de un PR que casi llega a main. update puede perder datos en migraciones no triviales. En producción: validate + Flyway o Liquibase, siempre.

3. Ignorar los warnings de startup
open-in-view, lazy initialization desactivada sin justificación, beans con nombres duplicados — Spring Boot los loguea como WARN y la gente los ignora. Yo los ignoré. Me costó semanas de diagnóstico que se hubieran evitado con diez minutos de leer los logs del primer deploy.

4. No separar profiles por ambiente
application.properties único para todo. En Lakaut Hub arrancamos así. El problema es que los values de dev (heap pequeño, pool mínimo, logging verbose) llegan a producción por default. La separación application-production.yml con los valores correctos es obligatoria desde el día uno, no cuando el problema ya está.


FAQ — Spring Boot en producción real

¿Qué tamaño de datasource pool recomendás para Railway con PostgreSQL?
Depende del plan de Railway y de los límites de conexiones del servidor Postgres. Como punto de partida: maximum-pool-size entre 5 y 10, minimum-idle en la mitad. La fórmula de HikariCP sugiere (núcleos * 2) + spindle_disks, pero en Railway tenés que medir qué límite de conexiones tiene el plan contratado y no superarlo entre todas las instancias.

¿ZGC o G1GC para Spring Boot en contenedores?
Para Java 21+ en contenedores con heap acotado (512MB - 2GB), ZGC Generational es mi elección actual. G1GC funciona bien con heaps grandes (4GB+). Con memoria limitada en Railway, las pausas de G1GC se vuelven impredecibles. Medí el cambio en Lakaut Hub y la diferencia fue clara en startup time y latencia p99.

¿Cómo diagnosticás un problema de pool de conexiones en producción sin acceso directo a la base?
Spring Boot Actuator con el endpoint /actuator/metrics/hikaricp.connections te da el estado del pool en tiempo real: activas, ociosas, pendientes, timeouts. Si hikaricp.connections.pending sube, el pool está saturado. Si hikaricp.connections.timeout tiene valores distintos de cero, ya tuviste timeouts reales.

¿@Transactional en la capa de Controller o solo en Service?
Solo en Service. Nunca en Controller. El Controller no debería saber nada sobre transacciones — mezclar concerns ahí rompe la separación de capas y hace más difícil testear la lógica de negocio de forma aislada. En Lakaut Hub tenemos esta regla como parte del code review checklist.

¿Qué diferencia hay entre liveness y readiness en Spring Boot Actuator?
liveness responde si la app está viva (si falla, Railway/Kubernetes la reinicia). readiness responde si está lista para recibir tráfico (si falla, Railway le saca el tráfico pero no la reinicia). Para el warmup de caches y el gap de startup, readiness es el que te importa. Configurar solo health genérico sin separar estos dos estados es perder la mitad del valor del health check.

¿Vale la pena Spring Boot para proyectos pequeños o es overkill?
Depende del contexto. Para Lakaut Hub, donde el dominio es complejo (PKI, certificados X.509, CRLs, TSA), el ecosistema de Spring Security, Spring Data JPA y la integración con Bouncy Castle justifican el overhead. Para un CRUD simple con tres endpoints, probablemente Quarkus o Micronaut arrancan más rápido y consumen menos memoria. La pregunta no es "¿es Spring Boot bueno?" sino "¿qué necesita este problema específico?".


Conclusión: la doc es el punto de partida, no el destino

Tres años trabajando con Spring Boot en producción real en Lakaut AC me dejaron una convicción: la documentación oficial es una guía de inicio, no un manual de operaciones. Está escrita para que la app arranque. No para que sobreviva un recital de Soda Stereo.

Los cuatro gotchas que documenté acá — open-in-view y el pool bajo carga, JVM tuning para contenedores en Railway, proxies de @Transactional y auto-invocación, y el gap de readiness en rolling restarts — no son bugs de Spring Boot. Son decisiones de diseño que tienen sentido en el contexto para el que fueron tomadas. El problema es que ese contexto no es el de producción real en una plataforma PaaS con recursos limitados.

Lo que no compro del ecosistema Spring en 2026 es la tendencia a esconder complejidad debajo de defaults que parecen mágicos. open-in-view=true por default es un diseño pensado para que los tutoriales funcionen sin configuración extra. En producción real, ese default cobra. El disclaimer en el log es útil, pero insuficiente.

Lo que sí acepto: cuando Spring Boot está bien configurado y bien entendido, es un stack sólido para dominios complejos. Lakaut Hub corre en Railway con JVM 21, PostgreSQL, y los gotchas documentados acá están resueltos. El sistema emite certificados digitales en producción todos los días. La doc no me llevó ahí — los logs sí.

Si trabajás con Java en producción y encontraste otros gotchas que no están acá, me interesa saberlo. Estoy construyendo categoría Java en el blog desde la evidencia, no desde los tutoriales. Hay mucho más por documentar.


Si el tema de diagnóstico en producción con logs reales te resulta útil, también documenté el análisis de guardrails reales para agentes autónomos después de un incidente concreto — el approach de evidencia primero aplica igual para sistemas distribuidos complejos.


Este artículo fue publicado originalmente en juanchi.dev