Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring @Lookup: obtain a fresh prototype from a singleton method

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Method injection lets a container-created singleton request a new prototype on each call, with subclassing limits that must be tested.

The lifetime mismatch is real

A singleton receipt renderer needs an isolated mutable render session for each document. Direct constructor injection of a prototype creates one session when the singleton starts, then reuses it. An @Lookup method asks the container to supply the session when rendering begins. ObjectProvider is often simpler because the dependency is explicit; use method injection when the collaborator must remain hidden behind a protected factory method.

Container creation is required

Spring implements lookup method injection by overriding a method on a generated subclass. The host class and lookup method cannot be final. A class built by an ordinary @Bean factory method does not get this lookup override because the container did not construct the instance in the required way. Register the host as a scanned component and mark the session prototype. A lookup of a singleton target still returns the same target each time; the annotation does not change the target scope.

Test identity and cleanup

Render two receipts and assert distinct session identities. Run parallel renders and check that temporary row buffers never cross documents. The container creates prototype instances, but it does not automatically run their destruction callbacks at the end of your method; explicitly close a session that owns a file or socket. Cleanup is a separate contract, and a unit test that directly calls an abstract lookup method cannot prove the runtime subclass was installed.

Implementation contract

Java
@Component
abstract class ReceiptRenderCoordinator {
    byte[] render(ReceiptDocument document) {
        return newSession().render(document);
    }

    @Lookup
    protected abstract ReceiptRenderSession newSession();
}

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class ReceiptRenderSession {
    private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

    byte[] render(ReceiptDocument document) {
        document.writePdfTo(buffer);
        return buffer.toByteArray();
    }
}

Cost and verification

Each call performs a container lookup and allocates a session. That is reasonable for document rendering, but a hot inner loop should not create a prototype per row or rely on automatic cleanup of external resources.

Common Mistakes

  • Do not mark the host class or lookup method final.
  • Do not instantiate the host through a normal @Bean factory method and expect lookup overriding.
  • Do not assume a prototype's destroy callback runs when the render method returns.

Read next

Spring ObjectProvider: deferred resolution without hiding ownership, Spring bean scopes: singleton identity is not thread safety, Spring singleton state: one bean does not mean one request at a time, Spring lifecycle cleanup: close container-owned resources, Spring @Component versus @Bean: choose who constructs the dependency.

spring
spring-boot
spring-core
lookup-method-prototype
Storage details