Multiple annotations of the same type on one element?
Asked Answered
H

8

101

I'm attempting to slap two or more annotations of the same type on a single element, in this case, a method. Here's the approximate code that I'm working with:

public class Dupe {
    public @interface Foo {
      String bar();
    }

    @Foo(bar="one")
    @Foo(bar="two")
    public void haha() {}
}

When compiling the above, javac complains about a duplicate annotation:

max@upsight:~/work/daybreak$ javac Dupe.java 
Dupe.java:5: duplicate annotation

Is it simply not possible to repeat annotations like this? Pedantically speaking, aren't the two instances of @Foo above different due to their contents being different?

If the above isn't possible, what are some potential workarounds?

UPDATE: I've been asked to describe my use case. Here goes.

I'm building a syntax sugarish mechanism to "map" POJOs to document stores such as MongoDB. I want to allow indexes to be specified as annotations on the getters or setters. Here's a contrived example:

public class Employee {
    private List<Project> projects;

    @Index(expr = "project.client_id")
    @Index(expr = "project.start_date")
    public List<Project> getProjects() { return projects; }
}

Obviously, I want to be able to quickly find instances of Employee by various properties of Project. I can either specify @Index twice with different expr() values, or take the approach specified in the accepted answer. Even though Hibernate does this and it's not considered a hack, I think it still makes sense to at least allow having multiple annotations of the same type on a single element.

Heroin answered 12/10, 2009 at 11:49 Comment(5)
There is an effort to have this duplicate rule relax to permit your program in Java 7. Can you describe your use case please?Statampere
I've edited my question with a description of why I want to do this. Thanks.Heroin
It could be handy in CDI to allow a bean to be provided for multiple qualifiers. For example I've just tried to reuse a bean in two places by qualifying it "@Produces @PackageName("test1") @PackageName("test2")"Synecious
Further: The answer below does not solve that problem because CDI would see the composite as one Qualifier.Synecious
@MaxA. Please change your "green check" acceptance to the correct Answer by mernst. Java 8 added the ability to repeat annotations.Emilie
I
147

Note: This answer is partially outdated since Java 8 introduced the @Repeatable annotation (see answer by @mernst). The need for a @Foos container annotation and dedicated handling still remain though.

Two or more annotations of same type aren't allowed. However, you could do something like this:

public @interface Foos {
    Foo[] value();
}

// pre Java 8
@Foos({@Foo(bar="one"), @Foo(bar="two")})
public void haha() {}

// post Java 8 with @Repeatable(Foos.class) on @Foo
@Foo(bar="one") @Foo(bar="two")
public void haha() {}

You'll need dedicated handling of Foos annotation in code though.

Interinsurance answered 12/10, 2009 at 11:56 Comment(5)
Can you also do this in Groovy?Trev
@Excel20 Yes. You'd have to use square brackets though, e.g. @Foos([@Foo(bar="one"), @Foo(bar="two")]). See groovy.codehaus.org/Annotations+with+GroovyInterinsurance
A bit late in the day but care to point to the advice that can process the list of Foo inside Foos? At the moment I am trying to prcess the result of a method but although the Foos is intercepted the Foo advice is never enteredGremial
Update: As of Java 8, this Answer is outdated. See the correct modern Answer by mernst. Java 8 added the ability to repeat annotations.Emilie
Is possible to add a "@Foo" value as a constant? or how you can access each one? I try to use this something like "@StringDef" and define an "@interface" from another.Paniagua
R
75

Repeating annotations in Java 8

In Java 8 (released in March 2014), it is possible to write repeated/duplicate annotations.

See tutorial, Repeating Annotations.

See specification, JEP 120: Repeating Annotations.

Reinhold answered 12/9, 2012 at 18:51 Comment(1)
Official document docs.oracle.com/javase/tutorial/java/annotations/repeating.htmlCounsel
M
28

Apart from the other ways mentioned, there is one more less verbose way in Java8:

@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Foo {
    String value();

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FooContainer {
        Foo[] value();
        }

@Foo("1") @Foo("2") @Foo("3")
class Example{

}

Example by default gets, FooContainer as an Annotation

    Arrays.stream(Example.class.getDeclaredAnnotations()).forEach(System.out::println);
    System.out.println(Example.class.getAnnotation(FooContainer.class));

Both the above print:

@com.FooContainer(value=[@com.Foo(value=1), @com.Foo(value=2), @com.Foo(value=3)])

@com.FooContainer(value=[@com.Foo(value=1), @com.Foo(value=2), @com.Foo(value=3)])

Morales answered 22/2, 2015 at 17:54 Comment(2)
It is worth pointing out that method/field name in FooContainer has to be strict named "value()". Otherwise Foo will not compile.Kashakashden
...also containing annotation type (FooContainer) cannot be applicable to more types than repeatable annotation type (Foo).Kashakashden
V
18

http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html

Starting from Java8 you can describe repeatable annotations:

@Repeatable(FooValues.class)
public @interface Foo {
    String bar();
}

public @interface FooValues {
    Foo[] value();
}

Note, value is required field for values list.

Now you can use annotations repeating them instead of filling the array:

@Foo(bar="one")
@Foo(bar="two")
public void haha() {}
Vladamar answered 19/3, 2014 at 16:34 Comment(0)
M
12

As said by sfussenegger, this isn't possible.

The usual solution is to build an "multiple" annotation, that handles an array of the previous annotation. It is typically named the same, with an 's' suffix.

By the way, this is very used in big public projects (Hibernate for example), so it shouldn't be considered as a hack, but rather a correct solution for this need.


Depending on your needs, it could be better to allow your earlier annotation to handle multiple values.

Example:

    public @interface Foo {
      String[] bars();
    }
Mundford answered 12/10, 2009 at 12:3 Comment(2)
I knew this was eerily familiar. Thanks for refreshing my memory.Heroin
It's now possible with Java 8.Adequacy
A
4

combining the other answers into the simplest form ... an annotation with a simple list of values ...

@Foos({"one","two"})
private String awk;

//...

public @interface Foos{
    String[] value();
}
Anesthesia answered 21/11, 2013 at 1:32 Comment(0)
I
3

If you have only 1 parameter "bar" you can name it as "value". In this case you wont have to write the parameter name at all when you use it like this:

@Foos({@Foo("one"), @Foo("two")})
public void haha() {}

a bit shorter and neater, imho..

Inorganic answered 16/4, 2012 at 14:43 Comment(2)
Correct point, but how does this attempt to answer OP's question?Adenoidal
@MouseEvent you are right, i think it was more of an improvement of the top answer from sfussenegger and thus belongs more in the comments up there. But the answer is outdated anyway due to repeatable annotations...Inorganic
C
0

In the current version of Java, I was able to resolve this issue with the following annotation:

@Foo({"one", "two"})
Creamcups answered 27/11, 2018 at 13:59 Comment(1)
which version? jdk 8,9,10,11?Lagrange

© 2022 - 2024 — McMap. All rights reserved.