spring.jpa.open-in-view

Keeps a JPA EntityManager open for the whole web request; it is on by default, and REST APIs are usually better off turning it off.

Signature
spring.jpa.open-in-view=false
  • Added in Spring Boot Spring Boot 4.1 (verified present)

Parameters

ParameterTypeDescription
$valueoptionalBoolean= truetrue keeps the persistence context open until the response is written, so lazy associations load anywhere, including during JSON serialisation. false closes it when the outermost transaction ends.

Return value

n/aWhether Boot registers OpenEntityManagerInViewInterceptor, which binds a JPA EntityManager to the thread for the entire processing of each web request.

Errors

With false, touching an unloaded lazy association outside a transaction throws LazyInitializationException.

Open Session in View (OSIV) keeps the JPA persistence context alive from the moment a request arrives until the response is written. With it on, a controller can return an entity and Jackson can walk its lazy collections while serialising, because Hibernate can still run queries. Spring Boot turns it on by default and, unless you set the property explicitly, logs a warning at startup:

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

Why APIs usually turn it off

The convenience hides the cost. Queries run outside your service layer and outside any transaction, often one per lazy association per item (the N+1 problem), and they run while the response is being written, where nobody looks for them. The request also holds a database connection for longer than it needs. Under load that is what drains the connection pool: slow clients reading a large response keep connections that the service layer finished with long ago.

It also blurs a boundary. When serialisation can fetch anything, the JSON your API returns depends on which fields of the entity happen to be reachable, and a new association added for an internal reason can leak into the response or trigger a cycle.

What changes when you set it to false

spring.jpa.open-in-view=false

The persistence context now closes when the outermost @Transactional method returns. Any lazy association that was not loaded by then throws LazyInitializationException when touched. That exception is the point: it shows you every place that relied on hidden queries. Fix each one in the service layer:

  • Map entities to record DTOs inside a @Transactional(readOnly = true) method, so everything the response needs is read while the transaction is open.
  • Fetch what you need in one query with @EntityGraph or a join fetch query.
  • Use a projection when you only need a few columns.

Switching an existing application is best done with a test suite that exercises the main endpoints, since each missing fetch only fails when that path runs. On a new project, set it to false on day one.

Example

Example · properties
# Close the persistence context with the transaction, not with the response.
spring.jpa.open-in-view=false

When to use it

  • Stopping slow clients from holding database connections while a large JSON response is written
  • Finding every hidden lazy-loading query in an existing API by switching OSIV off in a test profile
  • Keeping entity changes from leaking new fields into API responses

Frequently asked questions

How do I get rid of the 'spring.jpa.open-in-view is enabled by default' warning?

Set spring.jpa.open-in-view explicitly. false is the right choice for most REST APIs; true keeps the current behaviour and only silences the message.

Why do I get LazyInitializationException after setting open-in-view to false?

Code outside a transaction, often Jackson serialising an entity, is touching a lazy association that was never loaded. Load it inside the service with a fetch join or entity graph, or map to a DTO inside the transaction.

More examples

Reading inside the transaction and returning a DTO

The controller never sees an entity. Everything the response needs is copied into records inside the read-only transaction, so the code behaves the same whether OSIV is on or off, and Jackson cannot trigger a query while writing the body.

Example · java
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

@Entity
@Table(name = "orders")
class PurchaseOrder {
    @Id @GeneratedValue Long id;
    String customer;
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    List<OrderLine> lines = new ArrayList<>();
}

@Entity
class OrderLine {
    @Id @GeneratedValue Long id;
    String sku;
    int quantity;
    @ManyToOne(fetch = FetchType.LAZY) PurchaseOrder order;
}

interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Long> {
}

record OrderView(long id, String customer, List<LineView> lines) {
    record LineView(String sku, int quantity) {
    }
}

@Service
class OrderQueries {
    private final PurchaseOrderRepository orders;

    OrderQueries(PurchaseOrderRepository orders) {
        this.orders = orders;
    }

    @Transactional(readOnly = true)
    OrderView find(long id) {
        PurchaseOrder order = orders.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
        // The lazy collection is read here, while the persistence context is open.
        List<OrderView.LineView> lines = order.lines.stream()
                .map(l -> new OrderView.LineView(l.sku, l.quantity))
                .toList();
        return new OrderView(order.id, order.customer, lines);
    }
}

@RestController
public class OrderController {
    private final OrderQueries queries;

    OrderController(OrderQueries queries) {
        this.queries = queries;
    }

    @GetMapping("/api/orders/{id}")
    OrderView get(@PathVariable long id) {
        return queries.find(id);
    }
}

Fetching the association up front

An entity graph tells Hibernate to load the lines together with the invoice. Code that later reads invoice.lines outside the transaction finds them already loaded, so no LazyInitializationException and no extra query.

Example · java
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

@Entity
class Invoice {
    @Id @GeneratedValue Long id;
    @OneToMany List<InvoiceLine> lines = new ArrayList<>();
}

@Entity
class InvoiceLine {
    @Id @GeneratedValue Long id;
    String description;
}

public interface InvoiceRepository extends JpaRepository<Invoice, Long> {

    // One query with a join instead of one query per invoice when lines are read.
    @EntityGraph(attributePaths = "lines")
    Optional<Invoice> findWithLinesById(Long id);
}

What you see when a hidden query is exposed

The first line is the startup warning that shows OSIV was never configured. The exception is what a controller that relied on OSIV produces once it is off; the message names the exact association to fetch or map inside the service.

Example · text
WARN  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

# after spring.jpa.open-in-view=false, an endpoint that returned the entity directly fails with
org.hibernate.LazyInitializationException
# its message names the collection (com.example.orders.PurchaseOrder.lines) and says there is no session

Discussion

  • Be the first to comment on this lesson.