Skip to content

Support scope annotations on injectable constructor parameters and fields - #1949

Draft
davidboden wants to merge 2 commits into
google:masterfrom
davidboden:london/constructor-parameter-scope
Draft

davidboden wants to merge 2 commits into
google:masterfrom
davidboden:london/constructor-parameter-scope

Conversation

@davidboden

@davidboden davidboden commented Sep 19, 2026 •

Copy link
Copy Markdown

Status

This is a draft PR intended as a discussion point. The API and implementation are open to revision based on maintainer feedback.

Motivation

A dependency may already have an unscoped binding or provider, while one particular consumer needs that dependency to follow a narrower lifecycle. For example, a class may want one dependency instance per request without making every injection of that dependency request-scoped.

Today that generally requires declaring another provider or private binding in a module solely to add the scope. This proposal allows the consumer to express the requirement directly, either on an injectable constructor parameter or on an injected field:

@Inject
Handler(@RequestScoped RequestState requestState) {
  this.requestState = requestState;
}
class Handler {
  @Inject @RequestScoped RequestState requestState;
}

An injectable constructor or class already describes how Guice provides the containing class and which dependencies that provision requires. Allowing the constructor parameter or field to define additional behavior for those dependencies can remove module boilerplate while keeping the scope requirement next to the code that relies on it.

Proposed behavior

Scope annotations on injectable constructor parameters or injected fields create a local scoped provider around the dependency's existing binding.

  • The original binding is unchanged. Direct injections and other consumers continue to use its configured scope.
  • The scoped provider is isolated by consuming constructor or field, scope annotation, and dependency key.
  • Repeated construction through the same constructor, or repeated field injection on the same field, reuses the scoped value according to that scope.
  • Multiple parameters on the same constructor, or multiple fields on the same class, with the same dependency key and scope annotation share the same scoped provider.
  • A different consuming constructor or field receives a separate scoped provider, even when it requests the same dependency key.
  • If the dependency's existing binding is already scoped, the parameter or field scope wraps that binding; it does not remove the existing scope.

For @Singleton, the dependency therefore appears singleton only to objects created through that constructor, or only for that field. For @RequestScoped, it is reused by that constructor or field within one request and recreated in a later request.

The implementation uses the normal Guice scope registration and provider machinery, so custom scopes are supported when their annotations:

  • are marked with @ScopeAnnotation or jakarta.inject.Scope;
  • have runtime retention;
  • include PARAMETER or FIELD (as applicable) in their target; and
  • are registered with bindScope where required.

This PR adds PARAMETER and FIELD support to Guice's @Singleton and servlet @RequestScoped annotations. Method injection and provider-method parameters are unchanged.

Existing diagnostics are retained: multiple scope annotations on one parameter or field are rejected, and an unregistered custom scope reports No scope is bound to ....

Tests

Coverage is included for:

  • constructor-local singleton reuse;
  • field-local singleton reuse;
  • sharing between duplicate dependency parameters on one constructor, and between duplicate dependency fields on one class;
  • isolation from direct injection and other consuming constructors or fields;
  • custom scope annotations, for both parameters and fields;
  • duplicate and unregistered scope errors, for both parameters and fields; and
  • request-scoped reuse and request-to-request isolation.

Full build validation is still in progress while this remains a draft.

Discussion points

  • Is constructor-local and field-local scoping a useful addition to Guice's injection model?
  • Is (consuming constructor or field, scope annotation, dependency key) the right cache boundary?
  • Should parameter or field scoping wrap an already-scoped binding, as proposed here?
  • Error Prone's current MisplacedScopeAnnotations check assumes scope annotations on parameters or fields are ineffective. Adopting this syntax would require that check to recognize the new Guice behavior, or users would need a narrow suppression.

@google-cla

google-cla Bot commented Sep 19, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@davidboden
davidboden force-pushed the london/constructor-parameter-scope branch from b21340d to bb7ec65 Compare September 19, 2026 08:08
Comment thread extensions/servlet/src/com/google/inject/servlet/RequestScoped.java Outdated
@GedMarc

GedMarc commented Sep 19, 2026

Copy link
Copy Markdown

Possibly a candidate for https://github.com/GuicedEE/Services/tree/master/Google/guice-core ? (guicedee.com)
Would you be against that @davidboden ?

Allows @singleton and other @ScopeAnnotation-annotated annotations to be
applied directly to an injectable constructor's parameters, so the same
dependency can be scoped locally to that constructor without requiring a
separate binding declaration.

Also fixes the error ordering when a constructor parameter's scope
annotation is not bound: the "no scope is bound" error is now reported
before Guice attempts to resolve the dependency's binding, rather than
being masked by a misleading "no injectable constructor" error.
@davidboden
davidboden force-pushed the london/constructor-parameter-scope branch from bb7ec65 to ad2d7ac Compare September 20, 2026 06:53
Extends the existing constructor-parameter scoping support so that
scope annotations (e.g. @singleton, @RequestScoped) can also target
FIELD, allowing field-local scoping for injected dependencies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@davidboden davidboden changed the title Support scope annotations on injectable constructor parameters Support scope annotations on injectable constructor parameters and fields Sep 20, 2026
@davidboden

Copy link
Copy Markdown
Author

Possibly a candidate for https://github.com/GuicedEE/Services/tree/master/Google/guice-core ? (guicedee.com) Would you be against that @davidboden ?

Happy to take guidance. I'll take a look at the GuicedEE project; thanks for the pointer.

@GedMarc

GedMarc commented Sep 30, 2026

Copy link
Copy Markdown

@davidboden Sorry misunderstood - gave it an attempt - GuicedEE/Services#77

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants