javaadvanced
Custom Annotations and Reflection
Create and process custom annotations with reflection: field validation, auto-logging, and metadata.
javaPress ⌘/Ctrl + Shift + C to copy
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
// Define annotations
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface NotEmpty {
String message() default "Field must not be empty";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Range {
int min() default 0;
int max() default Integer.MAX_VALUE;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface LogExecutionTime {}
// Validator using reflection
public class AnnotationProcessor {
public static List<String> validate(Object obj) {
List<String> errors = new ArrayList<>();
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
Object value = field.get(obj);
// @NotEmpty check
if (field.isAnnotationPresent(NotEmpty.class)) {
NotEmpty ann = field.getAnnotation(NotEmpty.class);
if (value == null || (value instanceof String s && s.isBlank())) {
errors.add(field.getName() + ": " + ann.message());
}
}
// @Range check
if (field.isAnnotationPresent(Range.class)) {
Range ann = field.getAnnotation(Range.class);
if (value instanceof Integer v && (v < ann.min() || v > ann.max())) {
errors.add(field.getName() + ": must be between " + ann.min() + " and " + ann.max());
}
}
} catch (IllegalAccessException e) {
errors.add("Cannot access: " + field.getName());
}
}
return errors;
}
// Method execution timer via proxy
@SuppressWarnings("unchecked")
public static <T> T createTimedProxy(T target, Class<T> iface) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class<?>[]{iface},
(proxy, method, args) -> {
Method realMethod = target.getClass().getMethod(
method.getName(), method.getParameterTypes());
if (realMethod.isAnnotationPresent(LogExecutionTime.class)) {
long start = System.nanoTime();
Object result = method.invoke(target, args);
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.printf("%s() took %d ms%n", method.getName(), ms);
return result;
}
return method.invoke(target, args);
});
}
// Test it
static class UserForm {
@NotEmpty String name = "";
@NotEmpty(message = "Email required") String email = null;
@Range(min = 1, max = 150) int age = 200;
}
public static void main(String[] args) {
List<String> errors = validate(new UserForm());
errors.forEach(System.out::println);
// name: Field must not be empty
// email: Email required
// age: must be between 1 and 150
}
}Use Cases
- Custom validation frameworks
- AOP-style method interception
- Metadata-driven code generation
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
javaadvanced
Reflection Utilities — Inspect and Invoke
Inspect classes at runtime: list fields/methods, invoke methods, get annotations, and create instances.
Best for: Framework and library development
#java#reflection
kotlinadvanced
Annotations and Runtime Reflection
Work with Kotlin annotations and reflection: custom annotations, property inspection, and annotation processing.
Best for: Custom annotation-driven validation
#kotlin#annotations
javaintermediate
Spring Boot — Custom Validator Annotation
Create custom validation annotations with ConstraintValidator for domain-specific field validation.
Best for: Domain-specific input validation
#spring-boot#validation
javabeginner
Reverse a String in Java
Multiple ways to reverse a string in Java including StringBuilder, char array, and stream approaches.
Best for: String manipulation in coding interviews
#java#string