- Lab
- Core Tech

Guided: Java Annotations
This code lab will teach you how to use Java annotations to build a dynamic feature toggle system. You will learn to create and apply custom annotations, implement runtime logic to enable or disable features, and develop a compile-time processor to detect unused features. By the end of this lab, you will have practical experience leveraging annotations to create modular and maintainable solutions for real-world applications.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the lab Guided: Java annotations.
Annotations are a form of metadata that provide information about types and their members for:
- Declarative configuration. Annotations can replace XML configuration files, making the code more readable, self-documenting, and reducing boilerplate code.
- Compile-time checking. Annotations can enable compile-time error detection, validate code, or generate warnings for potential issues.
- Runtime processing. Annotations can also enable dynamic behavior based on metadata, implement cross-cutting concerns, and support dependency injection and aspect-oriented programming.
Java provides several built-in annotations that you might already be familiar with. For example, here are some annotations defined in the
java.lang
package:@Override
. Indicates that a method overrides a superclass method.@Deprecated
. Marks code as obsolete.@FunctionalInterface
. Indicates that an interface is a functional interface.
However, you can create your own annotations to:
- Add semantic meaning to your code
- Generate documentation
- Control method behavior
- Validate code at compile time
In this lab, you'll build a feature toggle system using custom annotations that will:
- Mark methods as toggleable features
- Group related features together
- Process annotations at runtime to control method execution
- Check for unused features during compilation
By the end of this lab, you'll have a solid understanding of runtime and compile-time annotation processing, two major uses of annotations in Java. ---
Familiarizing with the Program Structure
The application includes the following classes in the
src/main/java
directory:com.pluralsight.enums.Feature
: An enum that defines all possible feature names (e.g.,NEW_USER_REGISTRATION
,EMAIL_NOTIFICATION
, etc.).com.pluralsight.annotations.FeatureGroup
: A custom annotation used to categorize classes into feature groups.com.pluralsight.annotations.FeatureToggle
: A custom annotation that enables or disables methods based on feature names.com.pluralsight.processors.FeatureToggleProcessor
: A runtime annotation processor that scans the annotated methods of a class and executes those marked as enabled.com.pluralsight.validations.FeatureToggleAnnotationProcessor
: A compile-time annotation processor that detects and logs unused feature definitions.com.pluralsight.services.UserService
: A service class containing methods annotated with@FeatureToggle
and categorized using@FeatureGroup
.com.pluralsight.Main
: A class provided to run the application and demonstrate the feature toggle system in action.
The
Feature
enum and theMain
class are fully implemented. The other files are partially implemented, with placeholders for you to complete.You can compile and run the application using the Run button located at the bottom-right corner of the Terminal. Initially, the application will compile but will not execute any features.
Begin by examining the code to understand the program's structure. Once you're ready, start coding. If you encounter issues, a
solution
directory is available for reference. It contains subdirectories namedstep2
,step3
, and so on, each corresponding to a step in the lab. Within each subdirectory, solution files follow the naming convention[file]-[step]-[task].java
(e.g.,FeatureToggle-2-1.java
instep2
), where[file]
represents the file name, the first number indicates the step, and the second indicates the task. -
Challenge
Defining Custom Annotations
Custom annotations allow you to define your own metadata types that can be applied to code elements. Unlike classes or interfaces, annotations have a special syntax and are defined using the
@interface
keyword.To create a custom annotation, use the following syntax:
public @interface CustomAnnotation { // Annotation elements go here }
Annotation elements act like methods but with some key differences:
- They cannot have parameters.
- They cannot have a body.
- Their return types are limited to primitives,
String
,Class
, enums, annotations, and arrays of these types. - They can specify default values.
For example, if you define the following custom annotation:
public @interface CustomAnnotation { int number(); String message() default "Default message"; }
You can then apply it to a class, field, or method:
@CustomAnnotation(number = 3, message = "Hello, Custom Annotation!") public class AnnotatedClass { @CustomAnnotation(number = 6, message = "Field annotation example.") private String annotatedField; @CustomAnnotation(number = 9) public void annotatedMethod() { System.out.println("This method is annotated."); } }
If your annotation includes a single element named
value
, you can use a shorthand notation:// Annotation definition public @interface CustomAnnotation { String value(); } // Usage with shorthand @CustomAnnotation("Hello") public class AnnotatedClass {}
Marker annotations are annotations without elements. They serve as markers or flags. For example:
// Annotation definition public @interface RequiresLogging {} // Usage public class Service { @RequiresLogging public void processData() { // ... } }
Finally, meta-annotations control how your custom annotations behave. The two most important ones are:
-
@Retention
: Specifies how long the annotation is retained. Options include:RetentionPolicy.SOURCE
: The annotation is available only in the source code and discarded during compilation.RetentionPolicy.CLASS
: The annotation is recorded in the class file but not retained at runtime by the Java Virtual Machine (default if no@Retention
is specified).RetentionPolicy.RUNTIME
: The annotation is recorded in the class file and retained at runtime, making it accessible via reflection.
-
@Target
: Specifies the elements an annotation can be applied to. Without@Target
, an annotation can be applied to any element. Common options include:ElementType.TYPE
: For classes, interfaces, or enums.ElementType.METHOD
: For methods.ElementType.FIELD
: For fields.ElementType.PARAMETER
: For method parameters.ElementType.CONSTRUCTOR
: For constructors.
By applying these concepts, you can now create the custom annotations for the application.
-
Challenge
Implementing a Runtime Annotation Processor
A runtime annotation processor scans classes and their members for specific annotations and performs actions based on the annotation data.
The Java Reflection API allows you to examine and interact with annotations at runtime. To start, retrieve the
Class
object of an instance:Class<?> clazz = object.getClass();
Then, you can get all declared methods in the class:
Method[] methods = clazz.getDeclaredMethods();
This way, the processor can read annotation values from the methods and use them to control program behavior:
// Get the annotation instance CustomAnnotation annotation = method.getAnnotation(CustomAnnotation.class); // Retrieve a value from the annotation String operation = annotation.operation();
For example, you can invoke a method on an object based on the annotation data:
if (operation.equals("send")) { sendMethod.invoke(targetObject, methodParameters); }
You can also use the following method to check for an annotation:
// Check if an annotation is present boolean hasAnnotation = element.isAnnotationPresent(CustomAnnotation.class);
In the application, the
com.pluralsight.processors.FeatureToggleProcessor
class serves as a central component for managing active features at runtime. It implements a feature toggle system using annotations. Any class that employs these feature annotations can be processed dynamically by the staticexecuteFeatures
method to enable or disable specific functionality.This method is responsible for two main tasks:
-
Checking the class for a
@FeatureGroup
annotation:- This annotation helps organize and categorize related features.
-
Examining each method for
@FeatureToggle
annotations:- For every annotated method, the processor should:
- Determine if the feature is enabled.
- Execute the method if enabled, or log that the feature is disabled.
- For every annotated method, the processor should:
In the next tasks, you will complete the implementation of the
executeFeatures
method. -
-
Challenge
Annotating a Class
Annotations are applied to code elements using the
@
symbol followed by the annotation name. When an annotation includes multiple elements or a single element not namedvalue
, use parentheses and specify the element names explicitly:@CustomAnnotation( name = "example", enabled = true )
If an annotation has a single element named
value
, you can omit the element name for simplicity:@GroupName("Primary")
Applications often use service classes to encapsulate business logic and operations. In the feature toggle application:
- The
com.pluralsight.services.UserService
class groups user-related features. - Individual methods, such as
registerNewUser
, represent distinct features that can be enabled or disabled. - The
@FeatureToggle
annotation provides metadata indicating which features should be active.
Annotating service classes and their methods enables a declarative approach to:
- Categorize related functionality.
- Control the availability of specific operations.
- Manage feature rollouts dynamically without altering the codebase.
In the upcoming tasks, you will annotate the
UserService
class with@FeatureGroup
and@FeatureToggle
. - The
-
Challenge
Implementing a Compile-time Annotation Processor
Compile-time annotation processors run during the compilation phase, enabling you to:
- Validate code.
- Generate warnings.
- Create new source files.
- Prevent compilation if specific conditions are not met.
This approach provides early feedback to developers without introducing runtime overhead.
To create a compile-time processor, extend the
javax.annotation.processing.AbstractProcessor
class and implement its methods:@SupportedAnnotationTypes("com.example.CustomAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_21) public class CustomProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // Process annotations here return true; } }
In this example:
- The
@SupportedAnnotationTypes
annotation specifies the annotations the processor handles. - The
@SupportedSourceVersion
annotation declares the supported Java version. - The
process()
method is where the actual annotation processing occurs.
Within the
process()
method, the processor can:- Locate annotated elements using
roundEnv.getElementsAnnotatedWith()
. - Access annotation values.
- Report messages using
processingEnv.getMessager()
. This allows for levels such asNOTE
,WARNING
, andERROR
(errors cause the compilation to fail). For example:
processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, "Warning message" );
Once the processor is implemented, it must be registered by adding its fully qualified class name to a file named
javax.annotation.processing.Processor
located in theMETA-INF/services/
directory. For example, in this application, this file is located in thesrc/main/resourcesMETA-INF/services/
directory and its content is:com.pluralsight.validations.FeatureToggleAnnotationProcessor
Using a Processor with Maven
Since the application uses Maven as its build tool, there are two main approaches to incorporating a compile-time processor:
-
Separate Module Approach:
- Create a dedicated module for the processor.
- Add the processor module as a dependency in the project.
-
Same Project Approach:
- Configure the Maven compiler plugin to first compile the processor and then compile the rest of the source code.
For simplicity, the application adopts the second approach. The
pom.xml
file is configured with two executions of the Maven compiler plugin:- First Execution:
This execution compiles the processor without performing annotation processing. It runs in the
generate-sources
phase:
<execution> <id>compile-annotations</id> <phase>generate-sources</phase> <goals> <goal>compile</goal> </goals> <configuration> <proc>none</proc> <includes> <include>com/pluralsight/validations/FeatureToggleAnnotationProcessor.java</include> </includes> </configuration> </execution>
- Second Execution:
This execution compiles the remaining source files with annotation processing enabled. It runs in the
compile
phase:
<execution> <id>default-compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> <configuration> <proc>full</proc> </configuration> </execution>
In this setup:
- The first execution uses
proc=none
to compile only the annotation processor itself. - The second execution uses
proc=full
to process annotations in the rest of the code.
With Maven already configured and the
javax.annotation.processing.Processor
file in place, you only need to complete the implementation of theFeatureToggleAnnotationProcessor
class. This will enable compile-time validation of feature usage. -
Challenge
Conclusion
Congratulations on successfully completing this Code Lab!
To compile and run the application, you can either click the Run button in the bottom-right corner of the screen or use the Terminal with the following commands:
-
Compile and package the application:
mvn clean package
-
Execute the application:
java -cp target/feature-toggle-1.0-SNAPSHOT.jar com.pluralsight.Main
During compilation, the following warning message should appear in the output, indicating that the
CHANGE_PASSWORD
feature is not used in theUserService
class:[WARNING] Unused feature detected: CHANGE_PASSWORD
The program output should look similar to the following:
Executing features... Processing features in group: User Operations Registering a new user... Feature EMAIL_NOTIFICATION is disabled.
If you set
enabledByDefault
totrue
in the@FeatureToggle
annotation of theUserService.sendEmail
method, the program will execute the method the next time you compile and run it:Executing features... Processing features in group: User Operations Registering a new user... Sending an email... ``` --- ### Extending the Program Here are some ideas to further enhance your skills and extend the application's capabilities: 1. **Enhance the `@FeatureGroup` Annotation**: - Add metadata such as `version` or `author` to provide more context about feature groups. 2. **Strengthen the Compile-Time Processor**: - Implement checks to ensure consistent usage of `@FeatureGroup` and `@FeatureToggle`. For example, enforce that all methods in a class categorized by `@FeatureGroup` are annotated with `@FeatureToggle`. 3. **Support Complex Annotations**: - Add support for advanced features, such as conditional execution of methods based on parameters or contextual data. --- ### Related Courses in Pluralsight's Library If you'd like to continue building your Java skills or explore related topics, check out these courses available on Pluralsight: - [Java](https://app.pluralsight.com/paths/skill/java-se-17) These courses cover a wide range of Java programming topics. Explore them to further your learning journey in Java!
-
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.