Interview Prep Zoneby CuriouserLabs

Question 4 of 5 · staff level

What is Open Session in View? Why does Spring Boot enable it by default, and why do experienced teams turn it off?

🎤 Say this first

OSIV keeps the persistence context open for the entire HTTP request — including view rendering/JSON serialization — so lazy associations can still load after the service method returned. Boot defaults it on (spring.jpa.open-in-view=true, with a warning log) because it makes demos and small apps 'just work'. Teams disable it because it hides fetch-strategy bugs (lazy loads fire from the serializer — N+1 in the view!), holds a DB connection across the whole request including remote calls (pool exhaustion under slow downstreams), and blurs the transaction boundary that should define your unit of work. Disable it, and let LazyInitializationException point you at real fetch design.

The full picture

  • Mechanics: OpenEntityManagerInViewInterceptor binds an EntityManager to the request thread before the controller and closes it after response rendering; transactions still begin/end at @Transactional boundaries, but the context (and eventually a connection when lazy loads fire) outlives them.
  • The connection-holding trap: request → service TX ends → controller calls a 3s downstream API → serializer triggers lazy load → connection re-acquired/held while… multiply by concurrent requests = pool starvation with 'idle' CPUs. Classic prod incident shape.
  • With OSIV off: LazyInitializationException outside boundaries is a feature — a compile-time-ish signal that a use-case's fetch plan is undeclared. Answer it with fetch joins/entity graphs/DTO projections at the repository (previous question's toolbox).
  • Legit OSIV niche: small server-rendered CRUD apps (Thymeleaf) where developer velocity beats these concerns — defensible if chosen knowingly.
The failure OSIV was hiding
# application.yml
spring:
  jpa:
    open-in-view: false      # decision made explicit

// Now this blows up in tests instead of N+1-ing in prod:
@GetMapping("/orders/{id}")
OrderDto get(@PathVariable long id) {
    Order o = service.load(id);          // TX ended inside service
    return OrderDto.from(o);             // touches o.getLines() ->
}                                        // LazyInitializationException ✅ (told you!)
// Fix where it belongs: service returns a DTO built from a fetch-joined query.

🔄 Likely follow-up questions

  • Where exactly does the DB connection get acquired and released with OSIV on vs off?
  • How does the same concern appear in async controllers or WebFlux?
  • What's your migration plan for a large codebase currently relying on OSIV?