Atualizado: 09/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Processamento de Anotações em Tempo de Execução em Java

Uma anotação, por si só, não executa nenhuma ação. Ela apenas marca elementos do código (como classes, métodos ou campos) com metadados. Para que esses metadados tenham efeito, precisamos processá-los — geralmente usando reflexão ou ferramentas de processamento em tempo de compilação.

Aqui vamos focar no processamento em tempo de execução, feito com a API de reflexão (Reflection).


Interface AnnotatedElement

O pacote java.lang.reflect define a interface AnnotatedElement, que é implementada por várias classes da API de reflexão, como:

  • Class
  • Field
  • Method
  • Constructor
  • Parameter
  • Package

Isso significa que podemos inspecionar anotações em qualquer desses elementos.

Principais métodos:

T getAnnotation(Class<T> annotationClass)
T getDeclaredAnnotation(Class<T> annotationClass)
T[] getAnnotationsByType(Class<T> annotationClass)
T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

Esses métodos permitem verificar, obter e listar as anotações aplicadas a um elemento.


Exemplo simples: lendo uma anotação de classe

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)  // Disponível em tempo de execução
@Target(ElementType.TYPE)            // Pode ser usada em classes e interfaces
@interface MyAnnotation {
    String value();
}

// Aplicação da anotação
@MyAnnotation(value = "Thing")
class Thing {
    String name;
}

public class Program {
    public static void main(String[] args) throws Exception {

        var obj = new Thing();
        var cl = obj.getClass();

        // Obtém a anotação do tipo especificado
        MyAnnotation annot = cl.getAnnotation(MyAnnotation.class);

        System.out.println(annot); // Saída: @MyAnnotation(value=Thing)

        if (annot != null) {
            System.out.println(annot.value()); // Saída: Thing
        }
    }
}

O que acontece aqui:

  1. getClass() obtém o objeto Class associado a Thing.
  2. getAnnotation(MyAnnotation.class) retorna um proxy que representa a anotação aplicada.
  3. Chamando annot.value(), acessamos o valor definido na anotação.
  4. Caso a anotação não esteja presente, o método retorna null.

Obtendo várias anotações

Se um elemento possuir múltiplas anotações, podemos listá-las com getAnnotations():

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Description {
    String data();
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Name {
    String value();
}

@Name(value = "Thing")
@Description(data = "Represents some object with a name")
class Thing { }

public class Program {
    public static void main(String[] args) {

        var cl = Thing.class;

        // Retorna todas as anotações aplicadas
        Annotation[] annotations = cl.getAnnotations();

        for (var annot : annotations) {
            System.out.println(annot);
            System.out.println(annot.annotationType());
        }
    }
}

Saída:

@Name(value=Thing)
interface Name
@Description(data=Represents some object with a name)
interface Description

O método annotationType() retorna o tipo da anotação — útil para identificar ou processar diferentes tipos de metadados dinamicamente.


Exemplo prático: validação com anotações

Vamos criar um pequeno sistema de validação que usa anotações para definir regras de intervalo de valores numéricos.

1. Criando a anotação

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Range {
    int min();
    int max();
}

Essa anotação indica que um campo deve ter valor dentro de um intervalo definido.


2. Aplicando a anotação

class Person {
    String name;

    @Range(min = 1, max = 110)
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() { return name; }
    int getAge() { return age; }
}

Aqui, age deve estar entre 1 e 110.


3. Criando o validador com reflexão

import java.lang.reflect.Field;

class RangeValidator {

    public static boolean validate(Object obj) throws Exception {

        var cl = obj.getClass();

        for (Field field : cl.getDeclaredFields()) {

            // Verifica se o campo tem a anotação @Range e é do tipo int
            if (field.isAnnotationPresent(Range.class) && field.getType() == int.class) {

                Range range = field.getAnnotation(Range.class);
                field.setAccessible(true); // permite acessar campos privados
                int value = (int) field.get(obj);

                if (value < range.min() || value > range.max()) {
                    return false; // fora do intervalo
                }
            }
        }

        return true; // todos os campos válidos
    }
}

4. Testando o validador

public class Program {

    public static void main(String[] args) throws Exception {

        Person tom = new Person("Tom", 41);
        validatePerson(tom);

        Person bob = new Person("Bob", 141);
        validatePerson(bob);
    }

    static void validatePerson(Person p) throws Exception {
        if (RangeValidator.validate(p))
            System.out.printf("Person %s is valid%n", p.getName());
        else
            System.out.printf("Person %s is invalid!!!%n", p.getName());
    }
}

Saída:

Person Tom is valid
Person Bob is invalid!!!

O que acontece nos bastidores

  1. O método validate() obtém a classe do objeto com obj.getClass().
  2. Em seguida, percorre todos os campos declarados.
  3. Para cada campo, verifica se possui a anotação @Range com isAnnotationPresent().
  4. Se tiver, a anotação é obtida via getAnnotation().
  5. O valor do campo é lido com field.get(obj).
  6. Por fim, o método compara o valor com os limites definidos na anotação (min e max).

Esse padrão é amplamente usado em frameworks reais — por exemplo:

  • O Spring faz isso para injeção de dependências.
  • O Hibernate Validator usa anotações como @NotNull, @Size, @Min, @Max.

Resumo

ConceitoMétodo/ClasseDescrição
Inspeção de anotaçõesAnnotatedElementInterface base para classes, campos e métodos
Leitura de anotaçãogetAnnotation()Retorna uma instância de uma anotação específica
Listagem de todasgetAnnotations()Retorna todas as anotações aplicadas
Verificação de presençaisAnnotationPresent()Verifica se um elemento tem uma anotação
Exemplo práticoRangeValidatorDemonstra uso de reflexão para validação de dados

Conclusão

O processamento de anotações em tempo de execução é uma técnica poderosa que permite criar código declarativo e extensível. Com ela, é possível construir frameworks que “leem” as intenções do programador diretamente das anotações — um dos pilares de tecnologias modernas como Spring, Jakarta EE, JUnit, e Lombok.