error-prone icon indicating copy to clipboard operation
error-prone copied to clipboard

`UnnecessaryQualifier` outputs warnings for CDI observers

Open bannmann opened this issue 5 months ago • 0 comments

With the reproducer source file below, UnnecessaryQualifier in error-prone 2.40.0 reports the following warnings:

  1. [28,4] [UnnecessaryQualifier] A qualifier annotation has no effect here.
    • This is already reported as #5128.
  2. [56,42] [UnnecessaryQualifier] A qualifier annotation has no effect here.
    • This is a new issue. It's a false positive as Observes considers the qualifier:
      Each observer method must have exactly one event parameter, of the same type as the event type it observes. Event qualifiers may be declared by annotating the event parameter. When searching for observer methods for an event, the container considers the type and qualifiers of the event parameter.
    • The same false positive occurs with the popular @Observes @Initialized(ApplicationScoped.class) pattern, which emulates EJB's @Startup annotation. See https://www.adam-bien.com/roller/abien/entry/startup_initialization_logic_with_cdi.
package com.example;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.inject.Qualifier;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;

import com.google.errorprone.annotations.Keep;

@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Inject, access = AccessLevel.PROTECTED)
public final class FooDeleter
{
    @Deleted // ⚠️1
    private final Event<Foo> fooDeletedEvent;

    public void delete(int fooId)
    {
        log.info("Deleting Foo {}", fooId);

        fooDeletedEvent.fire(null);
    }
}

@Qualifier
@Target({ METHOD, FIELD, PARAMETER, TYPE })
@Retention(RUNTIME)
@interface Deleted
{
}

@Value
class Foo
{
    int id;
}

@Slf4j
final class DeletionObserver
{
    @Keep
    private void afterFooDelete(@Observes @Deleted Foo deletedFoo) // ⚠️2
    {
        log.info("Observed deletion of {}", deletedFoo);
    }
}

bannmann avatar Jul 23 '25 10:07 bannmann