3

I have a Spring1 @Controller annotated class with @RequestMapping annotated methods. I want to reference values for the @RequestMapping parameters, value and method, from another class, rather than hard coding them into the annotation.

Example

Instead of

@Controller
public class MyController {
    @RequestMapping(value="my/path", method=RequestMethod.GET)
    public String handlePath() {
        // etc...
    }
}

I want two files,

@Controller
public class MyController {
    @RequestMapping(value=Constants.PATH, method=Constants.PATH_METHOD)
    public String handlePath() {
        // etc...
    }
}

and

public class Constants {
    public static final String PATH = "my/path";
    public static final RequestMethod PATH_METHOD = RequestMethod.GET;
}

Unfortunately, this fails with the following compile-time error:

error: an enum annotation value must be an enum constant
        @RequestMapping(value=Constants.PATH, method=Constants.PATH_METHOD)
                                                              ^

Question

Why does this work in the case of String but does not work for enums?


Notes

  1. This question is not Spring specific, this is just an (hopefully) accessible example of this issue.
  2. I happen to be using Java 8
kuporific
  • 10,053
  • 3
  • 42
  • 46

1 Answers1

4

We need to look at what the Java Language Specification says is an acceptable value for an annotation method.

It is a compile-time error if the element type is not commensurate with the element value. An element type T is commensurate with an element value V if and only if one of the following is true:

  • If T is a primitive type or String, then V is a constant expression (§15.28).
  • If T is an enum type (§8.9), then V is an enum constant (§8.9.1).

PATH_METHOD is not an enum constant. RequestMethod.GET is an enum constant. For String, this

public static final String PATH = "my/path";

is a constant variable, which is a constant expression and therefore can be used.

It shouldn't work even if the constant was declared in the same file. Please review.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Ah, you're right; I knew the `String` part compiled, so I assumed the `enum` would too but it doesn't, shame on me (edited question slightly to account for this). I guess this is because "`enum` constant" has a stronger definition than "constant expression"? Half of the original question still stands: what's the difference when the String is defined inside and outside the particular class? (Maybe this is a bug in javac?) And a harder question: why are annotations designed this way? – kuporific Nov 11 '14 at 01:44
  • @kuporific Edited for `String` case. Again, it's not about where the value is defined, it's about the characteristics of the value. – Sotirios Delimanolis Nov 11 '14 at 01:46
  • @kuporific I don't have an answer for the why. – Sotirios Delimanolis Nov 11 '14 at 01:48
  • 1
    @kuporific: there is no difference between inside and outside declarations; the `String` part works for me so you might recheck whether you really did the setup right. The question about why is not so hard: annotations are compile-time constants and part of the class invariants, just like a `public` modifier, for example. They may be processed by development tools without actually executing the code. And since annotations are compile-time constants they must be composed of compile-time constants only. – Holger Nov 11 '14 at 09:59
  • @Holger "there is no difference between inside and outside declarations" you're right, the code I was playing with was slightly more complicated, and had a separate issue (edited question). I see that my issue is with how Java is *defined*, but I don't see a technical reason why the the language couldn't be defined such that the enum case worked. – kuporific Nov 11 '14 at 16:14
  • @kuporific: that’s a different question. I just answered to “why are annotations designed this way?” The other question is “why are `enum` constants not compile-time constants” for which the most likely answer is: “just historical reasons”. `enum`s where introduced in Java 5 but the definition of compile-time constants did not change. The possibility to refer to `enum` constants in `switch` statements or annotations was added as a separate language construct instead of redefining compile-time constants and that created this asymmetry. – Holger Nov 11 '14 at 16:31
  • @Holger It turns out "Why aren't `enum` constants compile-time constants?" is answered here: http://stackoverflow.com/questions/11773441/what-does-it-mean-to-say-that-int-enum-patterns-are-compile-time-constants If `enum`s *were* compile-time constants, consumers of an `enum` would have to be recompiled if the `enum` changed. – kuporific Nov 11 '14 at 17:02
  • @kuporific: no, that question is about (ab)using `int` values as enumerations. If the Java language supported `enum` values as compile-time constants it used the symbolic name rather than `int` values, just as `switch` statements, annotations and Serialization already do. Of course, changing a class declaring compile-time constants in an incompatible way will break classes using them, but that’s not different to `String` or primitive type compile-time constants. – Holger Nov 11 '14 at 17:09