66

In my project I use pre-defined annotation @With:

@With(Secure.class)
public class Test { //....

The source code of @With:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface With { 

    Class<?>[] value() default {};
}

I want to write custom annotation @Secure, which will have the same effect as @With(Secure.class). How to do that?


What if I do like this? Will it work?

@With(Secure.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secure {

}
Andrew Spencer
  • 15,164
  • 4
  • 29
  • 48
bvitaliyg
  • 3,155
  • 4
  • 30
  • 46

6 Answers6

37

As piotrek pointed out, you cannot extend Annotations in the sense of inheritance. Still, you can create Annotations that aggregate others:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SuperAnnotation {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SubAnnotation {
    SuperAnnotation superAnnotation();
    String subValue();
}

Usage:

@SubAnnotation(subValue = "...", superAnnotation = @SuperAnnotation(value = "superValue"))
class someClass { ... }
Halmackenreuter
  • 681
  • 5
  • 9
  • 47
    So instead of writing `@With(Secure.class)`, OP now has to write `@Secure(superAnnotation = @With(Secure.class))`? How does that help? – Matthew Read Mar 19 '18 at 16:27
28

From Java language specification, Chapter 9.6 Annotation Types:

No extends clause is permitted. (Annotation types implicitly extend annotation.Annotation.)

So, you can not extend an Annotation. you need to use some other mechanism or create a code that recognize and process your own annotation. Spring allows you to group other Spring's annotation in your own custom annotations. but still, no extending.

ajduke
  • 4,991
  • 7
  • 36
  • 56
piotrek
  • 13,982
  • 13
  • 79
  • 165
  • 4
    Probably 'extend' is not best expression for this. I mean implementation of auto-placement for argument. – bvitaliyg Mar 02 '14 at 11:25
  • 1
    @bvitaliyg: you can create your own annotation with any default fields but there is no out-of-the-box mechanism in jvm that will recognize it automatically. you need to write that code by yourself or check if existing libraries (like spring) are sufficient for your case – piotrek Mar 02 '14 at 11:27
  • 3
    For anyone looking to do this with Spring, it's worth knowing that the term Spring has for this is "meta-annotation". A "custom annotation" is just any annotation that you defined yourself, it doesn't necessarily mean that you meta-annotated it with a 3rd party annotation. – Andrew Spencer Oct 04 '18 at 11:46
12

To expand on Muhammad Abdurrahman's answer--

@With(Secure.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secure {

}

This does not work by default but you can use it in conjunction with Spring's AnnotationUtils.

See this SO answer for an example.

Eric Jiang
  • 544
  • 7
  • 13
9
@With(Secure.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secure {

}

This will work.

  • 1
    This is literally copied directly from the question. – mkobit Oct 13 '17 at 21:23
  • 6
    @mkobit Well spotted. Since the questioner asked whether this would work in their question, my answer gets straight to the point and actually answers the question. – Muhammad Abdurrahman Oct 25 '17 at 12:55
  • Yes, this is the correct answer to the question as asked... but only in the context of Spring, I believe (it only works if your annotation processors "walk" the annotation graph to the meta-annotation). – Andrew Spencer Oct 04 '18 at 11:42
  • 2
    See the [answer by Eric Jiang](https://stackoverflow.com/a/48028368/587365) – Andrew Spencer Oct 04 '18 at 11:49
  • 2
    This does not work. I'm trying to create my own annotation for fields on classes annotated with JPA's `@Entity`. I wanted my annotation to do the same as `@Transient` plus other stuff. – L. Holanda Apr 23 '19 at 22:06
1

You can use annotation for annotation like this:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithCustomUserSecurityContextFactory.class)
public @interface WithCustomUser {
  String username() default "demo@demo.com";
  String password() default "demo";
  String[] authorities() default {Authority.USER};
}

And define exact state in its "child"

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithCustomUser(username = "admin@admin.com",
                password = "admin",
                authorities = {Authority.USER, Authority.ADMINISTRATOR})
public @interface WithAdminUser {
}

In this case you have a some kind of "state" and access to the parent annotation fields via reflection/aspect.

  • This is exactly what I was looking for. I was wondering if it is necessary to change the ElementType if I want a "child"-annotation. Apparently I don't have to change it if it applies to the same type as the parent one. – Igor Aug 09 '21 at 13:11
1

So the provided answer from Eric Jiang is 100% working in my situation and she is: I need JMSListener ,but i want to hide the destination name:

@GetPlayerDataByUUIDListener
    public void getPlayerDataByUUID(Object message) {
        System.out.println("Im Here");
    }

`

@JmsListener(destination = PlayerStatisticsJMSConstants.GET_PLAYER_DATA_BY_UUID)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GetPlayerDataByUUIDListener {
}

So this is working perfectly ,and it is the same as:

@JmsListener(destination = "example")
    @GetPlayerDataByUUIDListener
    public void getPlayerDataByUUID(Object message) {
        System.out.println("Im Here");
    }
Georgi Peev
  • 912
  • 1
  • 14
  • 25