Definition
- Open-Session-In-View (OSIV) -- a pattern that keeps the Hibernate Session open for the entire HTTP request, not just the service-layer transaction.
- Enabled by default -- Spring Boot enables it via
OpenEntityManagerInViewInterceptorto reduce beginner friction. - Hides LazyInitializationException -- because the session stays open during rendering, lazy associations load on demand instead of throwing an exception.
- N+1 queries -- each lazy association Jackson touches during serialization fires a separate SELECT, outside the transaction.
- Connection held -- the database connection stays associated with the request through rendering, exhausting the pool faster under load.
- Blurred boundaries -- lazy selects run after the transaction has committed, in auto-commit mode, which is confusing and hides performance issues.
OSIV is like keeping the bank vault at National Bank of India open from the moment Priya walks in until she has walked all the way out and finished reading her printed statement in the lobby -- not just while Rajesh is actively serving her at the counter. It is convenient because Priya can ask for one more detail while reading in the lobby. But the vault is open far too long, Rajesh gets pulled away for ad-hoc lookups outside the controlled session, and the clean "service done, vault closed" boundary is lost.
Understanding the Concepts
What is OSIV and how does it work?
Normally the persistence context is bound to the service-layer transaction: it opens when the @Transactional method begins and closes when it commits. After that, entities are detached, and touching a lazy association throws LazyInitializationException. OSIV changes this by binding the session to the HTTP request via a servlet interceptor.
package com.bank.controller;
import com.bank.model.Account;
import com.bank.service.AccountService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AccountController {
private final AccountService accountService;
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
@GetMapping("/accounts/{id}")
public Account getAccount(@PathVariable String id) {
// With OSIV ON: session stays open during JSON serialization
// Jackson touches account.getTransactions() -> lazy load works
return accountService.findById(id);
}
}
With OSIV ON:
1. Request arrives, interceptor opens the Session
2. @Transactional service loads Account (transaction commits)
3. Controller returns Account to Jackson
4. Jackson serializes account.getTransactions() -- lazy load fires!
5. Session still open -- SELECT fires for each transaction
6. Response sent, interceptor closes the Session
Without OSIV:
3. Controller returns Account to Jackson
4. Jackson touches account.getTransactions() -- LazyInitializationException!
Why does OSIV hide LazyInitializationException?
The persistence context normally closes when the transaction ends. With OSIV, the session stays open for the entire request lifecycle, so when Jackson encounters a lazy proxy during serialization, it can still initialize it by issuing a SELECT. The exception never happens because the session is alive.
package com.bank.service;
import com.bank.model.Account;
import com.bank.repository.AccountRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
private final AccountRepository accountRepo;
public AccountService(AccountRepository accountRepo) {
this.accountRepo = accountRepo;
}
@Transactional(readOnly = true)
public Account findById(String id) {
return accountRepo.findById(id).orElseThrow();
// Transaction commits here, but OSIV keeps the session open
// Lazy associations can still be loaded during serialization
}
}
With OSIV ON:
- findById() loads Account with lazy List<Transaction>
- Transaction commits, entities stay managed (session still open)
- Jackson calls getTransactions() -- lazy proxy initializes
- SELECT * FROM transactions WHERE account_id = 'SAV2001'
- No exception -- feels convenient
The hidden cost:
- If Account has 100 transactions, that is one extra SELECT
- If the endpoint returns 50 accounts, that is 50 extra SELECTs (N+1!)
Why does OSIV cause N+1 queries during serialization?
Each lazy association Jackson touches fires a separate SELECT. For a list of accounts, each account's lazy transactions collection triggers its own query. This N+1 problem is invisible during development but devastating under production load.
package com.bank.controller;
import com.bank.model.Account;
import com.bank.service.AccountService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AccountListController {
private final AccountService accountService;
public AccountListController(AccountService accountService) {
this.accountService = accountService;
}
@GetMapping("/accounts")
public List<Account> listAccounts() {
return accountService.findAll();
// With OSIV: Jackson serializes each account
// Each account.getTransactions() fires a separate SELECT
// 50 accounts = 1 + 50 = 51 queries!
}
}
SQL log with OSIV ON (50 accounts):
1. SELECT * FROM accounts -- 1 query
2. SELECT * FROM transactions WHERE account_id = 'SAV2001' -- lazy
3. SELECT * FROM transactions WHERE account_id = 'SAV2002' -- lazy
...
51. SELECT * FROM transactions WHERE account_id = 'SAV2050' -- lazy
Total: 51 queries (N+1 problem)
Why does OSIV exhaust the connection pool?
With OSIV, the database connection stays associated with the HTTP request through the entire response rendering. A slow client downloading a large JSON response ties up a connection for the duration. Under peak traffic, this exhausts the connection pool.
Without OSIV:
Request ──► Service (connection held) ──► Controller ──► Response
│ │
Connection returned No connection needed
to pool here during rendering
With OSIV:
Request ──► Service ──► Controller ──► Response rendering
│ │
Connection held ───────────────┘
Connection returned to pool only after response sent
Peak traffic with 50 concurrent requests and pool size 10:
- Without OSIV: connections cycle quickly, all 50 served
- With OSIV: 10 connections held for entire response, 40 requests waiting
How do you disable OSIV?
Set spring.jpa.open-in-view=false in your application properties. This closes the session when the transaction ends, and any lazy access after that throws LazyInitializationException.
# application.properties -- the recommended production setting
spring.jpa.open-in-view=false
Expected behavior after disabling:
- Session closes when the @Transactional method returns
- Lazy access in the controller throws LazyInitializationException
- Forces you to fetch exactly what you need in the service layer
- This is GOOD -- it surfaces latent N+1 problems as clear exceptions
How do you fix LazyInitializationException after disabling OSIV?
Fetch exactly what the endpoint needs inside the transactional service layer using JOIN FETCH, entity graphs, or DTO projections.
package com.bank.repository;
import com.bank.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface AccountRepository extends JpaRepository<Account, String> {
// JOIN FETCH: load account + transactions in one query
@Query("SELECT a FROM Account a JOIN FETCH a.transactions WHERE a.id = :id")
Account findByIdWithTransactions(String id);
}
package com.bank.dto;
import java.math.BigDecimal;
// DTO projection: no entity, no lazy proxy, no OSIV needed
public record AccountSummary(String id, String holderName, BigDecimal balance) {}
package com.bank.repository;
import com.bank.dto.AccountSummary;
import com.bank.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AccountProjectionRepository extends JpaRepository<Account, String> {
// DTO projection: single query, no entities, no persistence context overhead
List<AccountSummary> findAllProjectedBy();
}
Fixes for LazyInitializationException:
1. JOIN FETCH: load associations eagerly in one query
2. Entity graph: @EntityGraph(attributePaths = {"transactions"})
3. DTO projection: return only the fields you need, no lazy proxies
4. All three are better than OSIV because they make the fetch explicit
What are common mistakes with OSIV?
The most common mistake is leaving OSIV on in production and not noticing the N+1 queries until connection pool exhaustion causes outages.
// WRONG: rely on OSIV for lazy loading
@GetMapping("/accounts/{id}")
public Account getAccount(@PathVariable String id) {
return accountService.findById(id);
// OSIV loads transactions lazily during serialization
// N+1 queries, connection held through rendering
}
// CORRECT: explicit fetch, OSIV disabled
@GetMapping("/accounts/{id}")
public AccountDetailDto getAccount(@PathVariable String id) {
return accountService.getAccountDetail(id);
// DTO built inside @Transactional with JOIN FETCH
// No lazy proxies, no OSIV needed
}
Common mistakes:
1. Leaving OSIV on in production -- hidden N+1 under load
2. Not noticing connection pool exhaustion until outage
3. Turning OSIV off late in the project -- surfacing dozens of lazy bugs at once
4. Best practice: disable OSIV on day one of the project
What should a senior engineer know about OSIV?
A senior engineer disables OSIV early in the project so lazy-loading problems surface as clear exceptions during development, not as mysterious slowness in production. They use JOIN FETCH and DTO projections to control exactly what data is loaded, and they treat LazyInitializationException as a signal to fix the fetch strategy, not a reason to re-enable OSIV.
package com.bank.service;
import com.bank.dto.AccountDetailDto;
import com.bank.repository.AccountRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductionAccountService {
private final AccountRepository accountRepo;
public ProductionAccountService(AccountRepository accountRepo) {
this.accountRepo = accountRepo;
}
@Transactional(readOnly = true)
public AccountDetailDto getAccountDetail(String id) {
// JOIN FETCH loads account + transactions in one query
var account = accountRepo.findByIdWithTransactions(id);
// DTO built here -- no lazy proxies cross the service boundary
return new AccountDetailDto(account);
}
}
Senior checklist:
1. Disable OSIV on day one: spring.jpa.open-in-view=false
2. Use JOIN FETCH or entity graphs for eager loading
3. Use DTO projections for read-only endpoints
4. LazyInitializationException = fix the fetch, not re-enable OSIV
5. Monitor connection pool usage under load
6. The warning 'spring.jpa.open-in-view is enabled' is telling you to disable it
Visual Overview
Request lifecycle WITH OSIV:
Request ──► Filter opens Session
│
▼
┌──────────────────────────────────┐
│ @Transactional Service Layer │
│ - Load Account (SELECT) │
│ - Transaction COMMITS │
└──────────────┬───────────────────┘
│ Session still open!
▼
┌──────────────────────────────────┐
│ Controller / Jackson │
│ - Serialize Account to JSON │
│ - Touch lazy transactions │
│ - N+1 SELECTs fire here! │
│ - OUTSIDE the transaction │
└──────────────┬───────────────────┘
│
▼
Filter closes Session, connection returned
Request lifecycle WITHOUT OSIV:
Request ──►
│
▼
┌──────────────────────────────────┐
│ @Transactional Service Layer │
│ - Load Account with JOIN FETCH │
│ - Build DTO │
│ - Session closes at TX end │
│ - Connection returned to pool │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Controller / Jackson │
│ - Serialize DTO (no proxies) │
│ - No database access │
│ - No connection held │
└──────────────────────────────────┘
Key Takeaway
Open-Session-In-View keeps the Hibernate Session open for the entire HTTP request, including JSON serialization, so lazy associations load on demand instead of throwing LazyInitializationException. Spring Boot enables it by default to reduce beginner friction. But in production it causes N+1 queries during serialization, holds database connections through the entire response, and blurs transaction boundaries by running lazy selects after the transaction has committed.
The fix is to set spring.jpa.open-in-view=false and fetch exactly what each endpoint needs inside the transactional service layer using JOIN FETCH, entity graphs, or DTO projections. These approaches make the data-loading explicit, predictable, and efficient. LazyInitializationException after disabling OSIV is not a problem to work around -- it is a signal that the fetch strategy needs to be fixed.
At National Bank of India, the customer detail endpoint returned an Account with a lazy list of transactions. With OSIV on, Jackson loaded them one lazy call at a time, producing an N+1 storm that only surfaced as slowness and connection pool exhaustion under peak traffic. Rajesh disabled OSIV, rewrote the endpoint to return a DTO built from a single JOIN FETCH query inside a read-only transaction, and the N+1 problem disappeared.
Remember one sentence: disable OSIV on day one and fetch what you need explicitly in the service layer -- LazyInitializationException is a signal to fix the fetch, not re-enable OSIV.
---