Backend Development 15 min read

Understanding Java Annotations and Reflection: Built‑in Annotations, Meta‑annotations, Custom Annotations, Class Loading and Runtime Reflection

This article explains Java annotations—including built‑in, meta‑ and custom annotations—covers how they are used in Spring, describes the Java reflection mechanism, class loading process, ways to obtain Class objects, and demonstrates retrieving generic and annotation information at runtime with code examples.

Java Captain
Java Captain
Java Captain
Understanding Java Annotations and Reflection: Built‑in Annotations, Meta‑annotations, Custom Annotations, Class Loading and Runtime Reflection

Annotations are not executable code but metadata that can be read by tools such as compilers or the JVM; they are declared with @AnnotationName and may include parameters (e.g., @SuppressWarnings(value="unchecked") ). Annotations can be placed on packages, classes, methods, fields, etc., and accessed via reflection.

Built‑in (common) annotations

@Override : indicates that a method overrides a superclass method.

@RequestMapping : maps web requests to handler methods; key attributes include value (URL), params (parameter filtering), etc.

@RequestBody : binds the HTTP request body to a method parameter and can be combined with @Valid for validation.

@GetMapping : shortcut for @RequestMapping(method = RequestMethod.GET) .

@PathVariable : binds a method parameter to a URI template variable.

@RequestParam : binds a method parameter to a request query parameter.

@ComponentScan : configures the packages that Spring should scan for components.

@Component , @Service , @Repository : mark classes as Spring beans with varying semantic intent.

Meta‑annotations

The Java API provides four meta‑annotations used to define other annotations:

@Target : specifies where the annotation can be applied (e.g., ElementType.TYPE , METHOD , FIELD ).

@Retention : defines the annotation’s lifecycle; RetentionPolicy.RUNTIME keeps it available at runtime for reflection.

@Documented : indicates whether the annotation should appear in generated Javadoc.

@Inherited : determines if the annotation is inherited by subclasses.

Custom annotations

Creating a custom annotation helps understand Spring’s internals. Example:

/**
 * Custom annotation example
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
}

Usage:

@MyAnnotation(value = "explanation")
public void test() { }

Reflection overview

Reflection allows a program to inspect and manipulate classes, fields, methods, and constructors at runtime. It is the key that makes Java a "quasi‑dynamic" language.

Key APIs include java.lang.Class , java.lang.reflect.Field , java.lang.reflect.Method , and java.lang.reflect.Constructor .

Class loading and ClassLoader

The JVM loads a class in three steps: Load (read bytecode and create a Class object), Link (verify and prepare), and Initialize (execute static initializers). Initialization occurs on active references such as new , static method calls, or reflective access.

There are three primary class loaders: BootstrapClassLoader, ExtensionClassLoader, and Application (System) ClassLoader. Example to obtain the system loader:

ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
ClassLoader parent = sysLoader.getParent();
System.out.println(sysLoader);
System.out.println(parent);

Obtaining a Class instance

Five common ways:

Using ClassName.class (compile‑time safe).

Calling object.getClass() on an existing instance.

Calling Class.forName("full.ClassName") with the fully‑qualified name.

Using the TYPE field of primitive wrapper classes (e.g., Integer.TYPE ).

Through a ClassLoader instance.

Runtime class structure

Once a Class object is obtained, you can retrieve all fields, methods, constructors, super‑classes, interfaces, and annotations. Example:

Class
c = Class.forName("com.example.User");
Field[] fields = c.getDeclaredFields();
for (Field f : fields) System.out.println(f);
Method[] methods = c.getDeclaredMethods();
for (Method m : methods) System.out.println(m);

Generic type information

Java erases generic types at compile time, but reflective APIs can recover them via ParameterizedType . Use getGenericParameterTypes() , getGenericReturnType() , and getActualTypeArguments() to inspect generic arguments of fields, method parameters, and return types.

Retrieving annotation data

Annotations on classes, fields, methods, and parameters can be accessed with isAnnotationPresent and getAnnotation . Example for field annotations:

for (Field f : User.class.getDeclaredFields()) {
    if (f.isAnnotationPresent(ApiModelProperty.class)) {
        System.out.println(f.getAnnotation(ApiModelProperty.class).value());
    }
}

Similar logic applies to method and parameter annotations.

The notes above constitute a concise reference for Java annotations and reflection, useful for developers working with Spring or any Java‑based framework.

JavareflectionSpringClassLoaderAnnotationsMeta‑annotation
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.