Spring Boot JPA — Why OSIV Killed Your Connection Pool (and How to Fix It)
Open Session in View (OSIV) is a silent connection pool killer in Spring Boot JPA.
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
- ✓Java 17+ (we'll use Java 21 features)
- ✓Spring Boot 3.x (but OSIV behavior applies to 2.x as well)
- ✓MySQL 8.0+ (or any relational DB with JPA)
- ✓Basic understanding of JPA, Hibernate, and connection pooling (HikariCP)
• OSIV (Open Session in View) keeps a JPA EntityManager open for the entire HTTP request, even after the service layer returns.
• This means database connections are held far longer than needed, starving your connection pool under load.
• Default behavior in Spring Boot 2.x and earlier: OSIV is enabled by default.
• Fix: Set spring.jpa.open-in-view=false in application.properties.
• Lazy loading exceptions after disabling OSIV? Use @Transactional in the service layer or fetch joins in JPQL.
Imagine you go to a restaurant and order a steak. The chef cooks it, brings it to your table, and then stands there holding the kitchen knife until you finish eating. No other chef can use that knife. That's OSIV — it keeps a database connection (the knife) hostage for the entire HTTP request, even after the data is already served.
If you've ever seen java.sql.SQLTransientConnectionException or HikariPool-1 - Connection is not available, request timed out after 30000ms, welcome to the club. I've debugged this exact issue at 2 AM on a Black Friday for a payment-processing platform handling 50,000 requests per minute. The root cause? A single property: spring.jpa.open-in-view=true.
Open Session in View (OSIV) is a pattern where the JPA EntityManager (or Hibernate Session) is kept open for the entire duration of a web request. Spring Boot enabled this by default in versions 2.x and earlier because it makes lazy loading transparent — you can access lazy associations in your view layer (JSP, Thymeleaf) without worrying about LazyInitializationException. But this convenience comes at a brutal cost: every HTTP request holds a database connection from the moment the transaction starts until the response is serialized. In a high-throughput SaaS billing system, that means your 100-connection HikariCP pool can handle only a fraction of the concurrent requests it should.
In this article, I'll show you exactly how OSIV kills your connection pool, how to detect it with metrics and logs, and the step-by-step migration to disable it safely. We'll cover real code, production incidents, and the patterns you need to replace lazy loading. By the end, you'll never look at spring.jpa.open-in-view the same way again.
What Is OSIV and Why Does Spring Boot Enable It by Default?
Open Session in View (OSIV) is a design pattern where the Hibernate Session (and its underlying JDBC connection) remains open for the entire duration of an HTTP request. In Spring Boot, this is implemented via the OpenEntityManagerInViewInterceptor or OpenSessionInViewInterceptor. The default behavior in Spring Boot 2.x and 3.x (until you change it) is to enable OSIV.
Why would Spring Boot do this? The answer is simple: backward compatibility with legacy web applications that use JSP, Thymeleaf, or other server-side view technologies. In those applications, the view layer often accesses lazy-loaded entity associations after the service layer has returned. Without OSIV, you'd get a LazyInitializationException because the Hibernate Session is closed when the transaction ends. OSIV keeps the session open so the view can lazily load data without explicit queries.
But here's the problem: in modern REST APIs, you don't have a view layer. You return JSON via Jackson. And Jackson serialization happens in the HTTP response phase, which is after the service layer transaction has committed. With OSIV, the Hibernate Session is still open during JSON serialization. If your entity has lazy associations, Jackson will trigger lazy loads during serialization — each of which requires a database query on the same connection. This means your connection is held for the entire request lifecycle, including network I/O for serialization.
What the Official Docs Won't Tell You
The official Spring Boot documentation mentions spring.jpa.open-in-view in a single sentence: "By default, Spring Boot enables the Open EntityManager in View pattern." That's it. No warning about connection pooling. No guidance on when to disable it. No explanation of the trade-offs.
What the docs don't tell you:
- OSIV turns every HTTP request into a long-lived database transaction. Even if your actual database work takes 5ms, the connection is held for the entire request (often 100-500ms). Under load, this means your connection pool is effectively 10-100x smaller than you think.
- OSIV masks lazy loading issues. When you disable OSIV, you'll immediately see
LazyInitializationExceptioneverywhere. This is a good thing — it forces you to write explicit fetch queries instead of relying on lazy loading in the view layer. Lazy loading in a REST API is almost always a code smell. - OSIV interacts badly with HikariCP's connection timeout. If your pool is exhausted, requests queue up waiting for a connection. With OSIV, each request holds a connection longer, so the queue grows faster. This creates a positive feedback loop that can bring down your entire application.
- OSIV bypasses your transaction boundaries. The Hibernate Session is kept open even after
@Transactionalmethods return. This means any lazy load triggered during serialization is not part of the original transaction — it's a separate, auto-committed query. This can lead to inconsistent reads or N+1 queries without you realizing it.
spring.jpa.open-in-view=false.Step-by-Step: How to Disable OSIV Safely
Disabling OSIV is a one-line config change, but it will break your application if you have lazy-loaded entities anywhere in your REST controllers. Here's the safe migration path:
Step 1: Add the configuration. Set spring.jpa.open-in-view=false in your application.properties or application.yml. This tells Spring Boot not to open a Hibernate Session for the entire request.
Step 2: Run your test suite. You'll likely see LazyInitializationException for any endpoint that returns entities with lazy associations. This is expected and desired.
Step 3: Fix each failing endpoint. There are three approaches: - Use @Transactional on the service method that loads the data. This keeps the session open for the duration of the method, but not the entire HTTP request. - Use JPQL fetch joins to eagerly load the required associations in the query itself. - Use DTOs (Data Transfer Objects) to avoid exposing entities directly. This is the cleanest approach for REST APIs.
Step 4: Test under load. Use a tool like Gatling or k6 to simulate concurrent requests. Compare the connection pool metrics before and after the change.
Step 5: Monitor in production. Deploy the change gradually (canary release) and watch your hikaricp_connections_active metric. It should drop dramatically.
Using @Transactional as a Replacement for OSIV
When you disable OSIV, the Hibernate Session closes when the @Transactional method returns. If you need lazy loading within a service method, you can use @Transactional to keep the session open for the duration of that method. This is a better pattern than OSIV because:
- The session is open only for the service method, not the entire HTTP request.
- You control exactly when the session is open and closed.
- You can use read-only transactions (
@Transactional(readOnly = true)) for queries, which can be optimized by Hibernate.
However, @Transactional is not a silver bullet. If you use it on a controller method, you're essentially re-introducing OSIV at the controller level. The correct pattern is to use @Transactional on service methods and return fully loaded DTOs or entities.
Here's the key: @Transactional on a service method ensures that all lazy loads within that method happen on the same connection. Once the method returns, the session closes. If you return an entity with lazy associations, the caller (controller) cannot access those associations without a new session. That's why DTOs are preferred — they don't have lazy associations.
Fetch Joins and @EntityGraph: Eager Loading Without Performance Pitfalls
The cleanest way to avoid lazy loading issues is to load all required data in a single query using fetch joins or @EntityGraph. This eliminates the need for lazy loading entirely and is more performant than N+1 queries.
JPQL Fetch Join: Use LEFT JOIN FETCH or JOIN FETCH in your JPQL query to eagerly load associations. This generates a single SQL query with JOINs, which is efficient for most use cases.
@EntityGraph: A declarative way to define fetch plans. You can define named entity graphs on your entity class and reference them in your repository methods. This is cleaner than raw JPQL for complex fetch strategies.
Important caveat: Fetch joins can cause performance issues if you fetch multiple collections in the same query (cartesian product problem). In that case, use multiple queries or batch fetching.
Here's a real-world example from a SaaS billing system where we needed to load invoices with their line items and payment transactions. Using fetch joins reduced the query count from 100+ to 1.
Detecting OSIV Issues in Production: Metrics and Logging
How do you know if OSIV is killing your connection pool? You need to monitor the right metrics. Here's what to look for:
1. HikariCP active connections vs. HTTP request duration. If the active connection count remains high for the entire duration of HTTP requests (not just the DB query time), OSIV is likely holding connections open. In Grafana, overlay hikaricp_connections_active with http_server_requests_seconds.
2. Connection pool pending count. If hikaricp_connections_pending is consistently above 0, your pool is exhausted. Check if the active connections correlate with HTTP request duration.
3. Thread dumps. Take thread dumps during peak load. Look for threads in BLOCKED state waiting on HikariPool.getConnection(). Count how many threads are in that state.
4. Slow query log. Enable MySQL's slow query log (set long_query_time = 0 temporarily). If you see many identical queries for lazy-loaded associations, that's the N+1 problem caused by OSIV.
5. Custom actuator endpoint. Create a custom endpoint that exposes the current HikariCP state, including which threads are holding connections.
hikaricp_connections_active and compare it to HTTP request duration. A high active count that correlates with request duration (not DB time) is a dead giveaway for OSIV.Advanced: OSIV with Multiple Data Sources and Read Replicas
OSIV becomes even more problematic when you have multiple data sources (e.g., primary + read replica) or complex routing. Spring Boot's OSIV interceptor applies to the default EntityManagerFactory. If you have multiple @Primary and @Secondary data sources, the interceptor might open sessions on the wrong data source or fail to close them properly.
Scenario: You have a primary data source for writes and a read replica for queries. You configure two DataSource beans and two EntityManagerFactory beans. With OSIV enabled, the interceptor will open a session on the primary data source for every request, even if the request only reads data. This means your read replica is underutilized, and your primary pool is exhausted by read requests.
Fix: Disable OSIV globally (spring.jpa.open-in-view=false) and use @Transactional(readOnly = true) with a routing data source to direct read queries to the replica. Each @Transactional method will open and close its own session on the appropriate data source.
Another issue: With OSIV, lazy loading always uses the original session's data source. If you load an entity from the primary data source and then try to lazy-load an association, it will query the primary again — even if the association could be served from the read replica.
Testing Your Fix: Load Testing and Validation
After disabling OSIV and fixing lazy loading issues, you need to validate the changes under load. Here's a testing strategy:
1. Unit tests. Ensure each repository method with fetch joins returns the correct data. Test that LazyInitializationException is not thrown in service methods with @Transactional.
2. Integration tests. Use @SpringBootTest with a real database (or Testcontainers) to test full request flows. Disable OSIV in the test profile and verify that endpoints return correct responses.
3. Load testing. Use Gatling or k6 to simulate production traffic. Measure: - Throughput (requests per second) - P50, P95, P99 latency - Connection pool metrics (active, pending, idle) - Error rate (should be 0%)
4. Comparison. Run the same load test with OSIV enabled and disabled. The difference in throughput is usually 5-10x.
5. Gradual rollout. Deploy the change to a canary instance first. Monitor for 24 hours. Then roll out to the rest of the cluster.
Here's a simple k6 script to test your endpoints.
The Black Friday Connection Pool Meltdown
spring.jpa.open-in-view=false. Replaced lazy loading in JPA entities with explicit fetch joins in JPQL queries and @EntityGraph annotations. Added @Transactional on service methods that needed lazy access.- Never trust default Spring Boot settings in production — especially OSIV.
- Monitor connection pool metrics (active, idle, pending) with Micrometer and Prometheus.
- Lazy loading is a code smell in REST APIs — use DTOs or fetch joins instead.
spring.jpa.open-in-view property. If true, disable it. Also check HikariCP metrics via Actuator.curl localhost:8080/actuator/metrics/hikaricp.connections.activecurl localhost:8080/actuator/health | jq .components.db| File | Command / Code | Purpose |
|---|---|---|
| OsivDefaultExample.java | spring.jpa.open-in-view=true // default | What Is OSIV and Why Does Spring Boot Enable It by Default? |
| OsivDetectionMetrics.java | @Configuration | What the Official Docs Won't Tell You |
| OsivMigrationExample.java | spring.jpa.open-in-view=false | Step-by-Step |
| TransactionalExample.java | @Service | Using @Transactional as a Replacement for OSIV |
| FetchJoinExample.java | @Entity | Fetch Joins and @EntityGraph |
| OsivDetectionActuator.java | @Component | Detecting OSIV Issues in Production |
| MultiDataSourceConfig.java | @Configuration | Advanced |
| LoadTest.java | export const options = { | Testing Your Fix |
Key takeaways
spring.jpa.open-in-view=false and fix lazy loading issues using @Transactional, fetch joins, or DTOs.Interview Questions on This Topic
What is OSIV and why is it considered an anti-pattern in REST APIs?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
That's Spring Boot. Mark it forged?
6 min read · try the examples if you haven't