Producers and qualifiers
You can put a scope annotation only on your own class. So how do you inject a java.time.Clock or a string with the site name? That is what a producer is for — a method marked @Produces that creates an object for injection. The container calls it every time something needs an object of that type.
A clock producer
Create the class src/main/java/com/example/blog/BlogProducers.java:
// src/main/java/com/example/blog/BlogProducers.java
package com.example.blog;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import java.time.Clock;
@ApplicationScoped
public class BlogProducers {
@Produces
Clock clock() {
return Clock.systemDefaultZone();
}
}Note the import: you need jakarta.enterprise.inject.Produces, not jakarta.ws.rs.Produces from Jakarta REST. Now the store can get the current date from the injected clock:
// src/main/java/com/example/blog/PostStore.java (fragment)
@Inject
Clock clock;
public Post add(NewPost data) {
long id = nextId.getAndIncrement();
Post post = new Post(id, data.title(), data.body(), LocalDate.now(clock));
posts.put(id, post);
return post;
}Why bother: in a test you can pass Clock.fixed(...) and always get the same date.
A qualifier
An application has many strings, and the container needs to know which one to inject. A qualifier is your own annotation that marks both the producer and the injection point:
// src/main/java/com/example/blog/SiteName.java
package com.example.blog;
import jakarta.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface SiteName {
}Add a second producer to BlogProducers:
// src/main/java/com/example/blog/BlogProducers.java (fragment)
@Produces
@SiteName
String siteName() {
return "Anna's blog";
}Injection
// src/main/java/com/example/blog/AboutResource.java
package com.example.blog;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.time.Clock;
import java.time.LocalTime;
@Path("/about")
@Produces(MediaType.TEXT_PLAIN)
public class AboutResource {
@Inject
@SiteName
String siteName;
@Inject
Clock clock;
@GET
public String about() {
return siteName + ", server time " + LocalTime.now(clock).withNano(0);
}
}curl http://localhost:8080/blog/api/aboutAnna's blog, server time 12:04:30
What else producers can do
| Feature | Example |
|---|---|
| Scope of the result | @Produces @ApplicationScoped — the object is created once |
| Producer field | @Produces @SiteName String siteName = "Anna's blog"; |
| Parameters | @Produces Report report(Clock clock) — the container injects clock |
| Cleanup | A method with @Disposes closes the created resource |
Without a scope annotation a producer works as @Dependent: a new object for every injection point.
Common mistake
Forgetting the qualifier at the injection point. If you write just @Inject String siteName;, the container looks for a string without a qualifier, finds none, and the application does not start:
WELD-001408: Unsatisfied dependencies for type String with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject com.example.blog.AboutResource.siteName
WELD-001475: The following beans match by type, but none have matching qualifiers:
- Producer Method [String] with qualifiers [@SiteName @Any] declared as [[BackedAnnotatedMethod] @Produces @SiteName com.example.blog.BlogProducers.siteName()],
The message hints at the fix: a bean of the right type exists, but with the @SiteName qualifier.