Making the Most of Java's Metadata

Learn how to use the metadata annotations provided in J2SE 5.0
by Jason Hunter

Learn how to use the metadata annotations provided in J2SE 5.0

The recent release of J2SE 5.0 (also known by its codename "Tiger") introduced numerous Java language changes designed to make programming in Java more expressive, developer-friendly, and safe. I covered many of Java's new features in a September 2003 article entitled "Big Changes Coming for Java ". One significant change I didn't cover—at the time it hadn't been fully sketched out—was Java's Metadata facility. In a new four-part series of articles, beginning with this one, I'll continue where I left off one year ago and show you how to make the most of Java's Metadata.

In this first article I'll explain the purpose of metadata and demonstrate how to use metadata annotations provided in the core J2SE libraries.

In the second article, I'll show how to write your own annotations (first writing simple ones like @Copyright and then looking at more advanced annotations like those built into the core language).

In the third article, I'll demonstrate how tools can use annotations at build-time (generating new source or supporting files) and how programs can use annotations at runtime (to change code behavior).

In the fourth and final article, I'll cover how authoring and deploying Web Services will be much easier in the future thanks to standard metadata annotations under development in JSR-181, for which Oracle is a member of the expert group. (Oracle is an active proponent of increased support for design-time metadata in development tools.)

Metadata

I admit when I first saw the proposal for JSR-175, " A Metadata Facility for the Java Programming Language ," (released in September 2004; Oracle is also a member of this expert group), I anticipated it producing yet another properties file that had to be placed in a JAR's META-INF directory or another XML deployment descriptor that had to be bundled next to JARs. Happily, that's not what metadata is about. In fact, it's just the opposite. Java's new metadata facility provides a standard way to annotate Java code from within Java code. It lets you put descriptive metadata right next to the element being described.

When talking about metadata you see several similar terms float by, so here's a little glossary to help you understand their differences:

Term Definition
Metadata Data about data. The goal of JSR-175 was to provide metadata facilities in the Java language.
Annotation A special kind of Java construct used to decorate a class, method, field, parameter, variable, constructor, or package. It's the vehicle chosen by JSR-175 to provide metadata.
Annotation Type A named variety of annotation, with a particular implementation
Attribute A particular metadata item that's being specified by an annotation. Sometimes used interchangeably with annotation


Example: A Fuji apple has the attribute that it's red. Assuming there's a FujiApple class you can specify its color using an annotation of the @Color annotation type. By doing that you've provided metadata about the apple.

The need for metadata in Java has been around since release 1.0. Java just never provided a standard mechanism to record metadata, and as a result we programmers have found various tricks and hacks to add metadata using the tools at our disposal. Some of the places you see metadata used in J2SE 1.4 and earlier:

  • The transient keyword
  • The Serializable marker interface
  • The SingleThreadModel servlet interface
  • Elements within the web.xml deployment descriptor
  • The META-INF/MANIFEST.MF file
  • The BeanInfo interface
  • The @deprecated Javadoc comment
  • All the XDoclet Javadoc tags.

When using these techniques you probably didn't think you were adding metadata, but that's in fact what you were doing. The problem with the above approaches is that they're all different ways of solving the same problem and don't generalize well. Each has at least one drawback that's being addressed by the new Metadata facility.

For some in this list, the limitations are obvious. Using keywords doesn't scale; you can't use users defining their own keywords. Marker interfaces don't provide any information except their existence (i.e. they don't take parameters) and they only work on classes, not fields or methods or packages.

Some others in the list may look reasonable. Using XML support files seems like a good idea, and in fact still is in many cases. But for many uses where we've used XML files, like to dictate which of a class's methods should be seen as web services, it would be more elegant to put the rules within the Java code next to the methods directly. With metadata you can let the XML descriptor files containing just the deployment-related decisions.

Probably the most elegant uses of metadata from the list are the @deprecated Javadoc comment and the XDoclet tags created in its image. That's probably why the JSR-175 syntax looks an awful lot like the @deprecated tag, as we'll see in the next section.

Annotations

Annotations are easy to attach to code constructs. You write an "at" sign (@) and then the annotation type name, placing the annotation (or annotations) directly before the item being annotated. Here's a simple example:




import javax.jws.WebService;
import javax.jws.WebMethod;

@WebService
public class HelloWorldService {

  @WebMethod
  public String helloWorld() {
    return "Hello World!";
  }
}

When deployed in the right environment, the addition of the @WebService and @WebMethod annotations instruct the web service environment to make this class into a web service.

You can annotate methods, classes, fields, parameters, variables, constructors, and even whole packages (using a special external package-info.java file). Annotations can take any number of named arguments within parentheses. Here's a more advanced example class decorated using annotations to make a web service. It includes a theoretical JNDI environment variable lookup:



@WebService(
  name = "PingService",
  targetNamespace="http://acme.com/ping"
)
@SOAPBinding(
  style=SOAPBinding.Style.RPC,
  use=SOAPBinding.Use.LITERAL
)
public class Ping {
  public @env double level = 500.0;  // JNDI lookup

  public @WebMethod(operationName = "Foo") {
    void foo() { }
  }
}

This example shows annotations attached to a class, a variable, and a method (there are actually two on the class). The @env annotation doesn't have any arguments and so it doesn't need any parentheses. The other annotations take one or more named parameters.

When you create a new annotation type you dictate which parameter names are allowed and their types. The types accepted by an annotation are strictly limited; they can only be primitives, String, Class, enum types, annotation types, and arrays of the preceding types. Passes parameters must always be non-null compile-time constants.

Understanding what effect the annotations shown in this example have will have to wait for the fourth article in this series. Let's start with a look at the simple annotation types provided with J2SE 5.0: @Override, @Deprecated , and @SuppressWarnings.

Built-In Annotations

As we look at the three standard user-level annotations, one has to wonder: with all the possible annotation types that could be provided, why did Tiger provide a scant three? The reason is that providing a multitude of standard annotations wasn't the goal.

The charter for JSR-175 strictly dictated it was to define a metadata facility. It's left to us as programmers to write custom annotation types and to other JSRs to write a set of standard annotation types. For example, there's a new JSR-250, titled "Common Annotations for the Java Platform," chartered to "develop annotations for common semantic concepts in the J2SE and J2EE platforms that apply across a variety of individual technologies." JSR-250 plans to deliver its standard set of annotations in the javax.annotations package sometime during the spring of 2005. There's also the aforementioned JSR-181, which would make it easier to write web services in a J2EE container (what we're covering in the fourth article in this series). In fact most new enterprise JSRs, from Servlets 2.5 to EJB 3.0 to JDBC 4.0, are considering what niceties annotations might provide.

@Override

The first J2SE standard annotation, @Override , lets you add a new optional compiler check to your code. Its presence on a method indicates that the method is intended to override a method in a superclass. If the compiler detects the method isn't really overriding anything, it's a compile error. With regular use, @Override helps you to avoid the subtle bugs you get when your method signatures don't exactly match — when your polymorphism becomes what you might call "unimorphism."

As an example, the following code may look quite reasonable:




public class OverrideExample {
  @Override
  public boolean equals(OverrideExample obj) {
    return false;
  }
}

However, when you compile OverrideExample.java you receive an error indicating a subtle problem.




% javac OverrideExample.java
javac OverrideExample.java
OverrideExample.java:3: method does not override a method from its superclass
  @Override
   ^
1 error

By giving the compiler the hint you were expecting an override that lets the compiler catch the subtle bug that the equals() method takes an Object type parameter.

Is the @Override annotation useful in the real world? Only if you're an ultra-disciplined programmer who's willing to mark every overriding method with @Override . How many of us can claim that level of discipline? I don't think I can. Perhaps IDEs will find a way to encourage or enforce @Override use.

@Deprecated

The second standard annotation is @Deprecated and has nearly identical behavior to the @deprecated Javadoc tag. You use it like this:



public class DeprecatedExample {
  @Deprecated
  public static void badMethod() {
  }
}

public class DeprecatedUser {
  public static void main(String[] args) {
    DeprecatedExample.badMethod();
  }
}

The @Deprecated annotation looks a lot like the @deprecated tag except it appears outside a comment, right before the method or class declaration, and has a capital "D". If you try to compile the above code javac produces a warning:



% javac Deprecated*.java
Note: DeprecatedUser.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
1 error


If you follow the warning's suggestion and compile with -Xlint:deprecation , you get details on the warning:



% javac -Xlint:deprecation
DeprecatedUser.java:3: warning: [deprecation] badMethod() in DeprecatedExample
has been deprecated
    DeprecatedExample.badMethod();

Is the @Deprecated annotation more useful than @Override ? I think not. The annotation doesn't support any arguments, so unlike the Javadoc tag you can't provide a string to explain the deprecation and recommend an alternative method to use. The @Deprecated annotation actually provides less value than the @deprecated tag. The only advantage with the annotation is that you can programatically detect deprecated items at runtime. For that reason, conventional wisdom says to use both the @deprecated tag and the @Deprecated annotation, one for documentation and the other for runtime reflection.

I believe it's unfortunate JSR-175 didn't choose to do more with @Deprecated. At minimum the annotation should have reproduced the @deprecated tag's ability to take a string explanation, something the compiler could output with its deprecation warning. With additional arguments, @Deprecated also could have accepted an "isError" boolean parameter to indicate whether use of the method was merely discouraged or should be considered a compile error (complete with a nice custom explanation explaining the reason for the error). Looking at C# for example one finds the attribute [Obsolete] that does exactly this and it proves quite useful.

@SuppressWarnings

The last J2SE-provided annotation is @SuppressWarnings . This annotation acts as a directive to the compiler telling it to be quiet about certain warnings within the annotated code element.

A bit of background: J2SE 5.0 adds several new features to the Java language and with them a plethora of new warnings and a promise to add more in the future. You can control the reporting of these warnings with -Xlint arguments to "javac" as shown above in the @Deprecated section.

By default the Sun compiler outputs warnings as simple two-liners. By adding an -Xlint: keyword flag like -Xlint:finally you can get a full explanation for errors of the keyword's type. By adding a dash before the keyword and writing -Xlint: -keyword you can suppress the warning. Here's a cheat sheet:

 

Keyword Purpose
deprecation Warn when using a deprecated class or method
unchecked Warn when performing an unchecked conversion, such as when using a collection without using generics to specify the type that the collection holds
fallthrough Warn when a switch block falls-through to the next case without a break
path Warn when there's a non-existent path in a classpath, sourcepath, etc.
serial Warn when there's a missing a serialVersionUID definitions on serializable classes.
finally Warn about any finally clause that cannot complete normally
all Warn about all of the above

The @SuppressWarnings annotation allows you to do selective suppression of warnings within particular code sections (i.e. classes or methods). The idea is that when you see a warning, you should investigate it, and if you determine it's not a problem, you add an @SuppressWarnings annotation so you won't see the warning anymore. Although it sounds like this could mask potential errors, it actually improves code safety because it keeps you from becoming insensitized to warnings — every warning you see should be worth looking into.

Here's an example using @SuppressWarnings to suppress a deprecation warning:



public class DeprecatedExample2 {
  @Deprecated
  public static void foo() {
  }
}

public class DeprecatedUser2 {
  @SuppressWarnings(value={"deprecation"})
  public static void main(String[] args) {
    DeprecatedExample2.foo();
  }
}

The @SuppressWarnings annotation accepts a "value" variable that's an array of strings indicating the warnings that should be suppressed. The set of legal strings varies by the compiler, but on the JDK it's conveniently the same set of keywords that can be passed to -Xlint . Compilers are required to ignore any keywords they don't recognize; handy if you use a few different compilers.

Because the @SuppressWarnings annotation only accepts one parameter and used the special name "value" for that parameter, you have the option to drop the value= as a convenient shorthand:




public class DeprecatedUser2 {
  @SuppressWarnings({"deprecation"})
  public static void main(String[] args) {
    DeprecatedExample2.foo();
  }
}

You can pass any number of string values in the single array parameter to the annotation and place the annotation at any level. For example, the following example code dictates that deprecation warnings be suppressed for the entire class, with unchecked and fallthrough warnings suppressed only within the main() method code:




import java.util.*;

@SuppressWarnings({"deprecation"})
public class NonGenerics {

  @SuppressWarnings({"unchecked","fallthrough"})
  public static void main(String[] args) {
    Runtime.runFinalizersOnExit();

    List list = new ArrayList();
    list.add("foo");
  }

  public static void foo() {
    List list = new ArrayList();
    list.add("foo");
  }
}

Is @SuppressWarnings more useful than the previous two annotations? Absolutely. However, the annotation isn't yet fully supported in the JDK 1.5.0 release and if you try it with 1.5.0 it acts like a no-op. Calling -Xlint:-deprecation also doesn't have any effect. Sun hasn't stated when support will be added but hinted it will happen in an upcoming dot release.

Going Further

If you tried to look at the Javadocs pages for these attributes you may have had a hard time finding them. They're in the core java.lang package but they're a bit hidden; they appear at the very bottom of the Javadoc class listing after Exceptions and Errors.

Notice the strange annotations @Target and @Retention attached to the SuppressWarnings annotation? These are called meta-annotations and describe where the annotation is applicable. I'll explain that, along with how to apply meta-annotations to your own annotations, the second article in this series.

Jason Hunter is author of Java Servlet Programming and co-author of Java Enterprise Best Practices (both O'Reilly). He's an Apache Member and as Apache's representative to the Java Community Process Executive Committee he established a landmark agreement for open source Java. He's publisher of Servlets.com and XQuery.com , an original contributer to Apache Tomcat, the creator of the com.oreilly.servlet library, and a member of the expert groups responsible for Servlet, JSP, JAXP, and XQJ API development. He co-created the open source JDOM library to enable optimized Java and XML integration. In 2003, he received the  Oracle Magazine Author of the Year award.