Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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/about
Anna's blog, server time 12:04:30

What else producers can do

FeatureExample
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
CleanupA 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.

Thanks for reading!

Donate to the site — we work hard for you.