The best examples of practical examples of Java annotations in real code

If you write Java for a living, you’re already using annotations everywhere, whether you think about them or not. But seeing **examples of practical examples of Java annotations** in real-world code is what actually helps you level up: why `@Override` catches bugs, how `@Transactional` saves you from half-written database rows, and why `@NotNull` keeps your API honest. In this guide, we’ll walk through realistic examples of Java annotations pulled from everyday enterprise work: Spring Boot services, JPA entities, validation layers, and even custom annotations you can ship in your own libraries. These examples of practical usage are opinionated, modern (Java 17+ and 2024-era frameworks), and focused on how annotations change your workflow, not just how they look in a tutorial. By the end, you’ll recognize where annotations shine, where they get overused, and how to design your own when frameworks don’t give you what you need. Think of this as a field guide: short, focused scenarios instead of abstract theory.
Written by
Jamie
Published
Updated

Real-world examples of practical examples of Java annotations

Let’s start where annotations actually earn their keep: in production-style code. These are not toy snippets; they’re the kind of examples of practical examples of Java annotations you’ll see in a Spring Boot microservice or a legacy-but-still-billing-customers monolith.

1. @Override and @Deprecated: catching bugs in everyday OOP

The simplest example of a Java annotation that pays off daily is @Override. It prevents silent bugs when method signatures drift.

public interface PaymentGateway {
    void charge(BigDecimal amount);
}

public class StripePaymentGateway implements PaymentGateway {

    @Override
    public void charge(BigDecimal amount) {
        // real implementation here
    }

    @Deprecated(since = "1.4", forRemoval = true)
    public void charge(double amount) {
        // legacy method kept for backward compatibility
    }
}

Two practical wins here:

  • @Override makes the compiler scream if you misspell charge or change the parameter type.
  • @Deprecated signals to teammates and tools that the double-based method is on its way out, and modern IDEs will highlight it.

This is one of the best examples where annotations directly reduce production risk with almost no mental overhead.

2. Validation annotations: @NotNull, @Email, @Size

Bean Validation (Jakarta Validation, formerly JSR 380) gives some of the clearest examples of practical examples of Java annotations because the intent is obvious and the behavior is enforced at runtime.

import jakarta.validation.constraints.*;

public record UserRegistrationRequest(
        @NotBlank(message = "Email is required")
        @Email(message = "Email must be valid")
        String email,

        @NotBlank(message = "Password is required")
        @Size(min = 12, message = "Password must be at least 12 characters")
        String password,

        @NotNull
        @Min(value = 18, message = "You must be at least 18")
        Integer age
) {}

In a Spring Boot controller:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public ResponseEntity<Void> register(@Valid @RequestBody UserRegistrationRequest request) {
        // If validation fails, Spring returns 400 with error details
        // Business logic here
        return ResponseEntity.ok().build();
    }
}

These validation annotations are real examples of declarative programming: instead of writing repetitive if (email == null || !email.matches(...)) checks, you annotate the model and let the framework handle it.

For background on annotation-based constraints, the Jakarta Bean Validation spec is a good reference: https://jakarta.ee/specifications/bean-validation/.

3. Spring REST annotations: @RestController, @GetMapping, @RequestParam

REST endpoints are some of the best examples of Java annotations in daily backend work. A small Spring controller can show several examples of practical examples of Java annotations in one place.

@RestController
@RequestMapping("/api/weather")
public class WeatherController {

    private final WeatherService weatherService;

    public WeatherController(WeatherService weatherService) {
        this.weatherService = weatherService;
    }

    @GetMapping
    public WeatherResponse getWeather(
            @RequestParam String city,
            @RequestParam(defaultValue = "F") String unit) {
        return weatherService.getCurrentWeather(city, unit);
    }
}

Here:

  • @RestController tells Spring to treat return values as JSON, not views.
  • @RequestMapping sets the base path.
  • @GetMapping declares the HTTP method and path.
  • @RequestParam binds query parameters to method arguments.

This is a clean example of how annotations turn what used to be verbose servlet code into a tiny, readable API surface.

4. JPA/Hibernate entity annotations: @Entity, @Id, @Column

ORM mappings are classic examples include JPA annotations that bridge Java objects and relational tables.

import jakarta.persistence.*;

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String customerEmail;

    @Column(nullable = false)
    private BigDecimal totalAmount;

    @Enumerated(EnumType.STRING)
    private OrderStatus status = OrderStatus.PENDING;

    // getters and setters omitted
}

These annotations are real examples of how metadata controls persistence behavior without XML:

  • @Entity marks the class as a managed entity.
  • @Table customizes the table name.
  • @Column fine-tunes constraints and column details.
  • @Enumerated defines how enums are stored.

The Jakarta Persistence specification documents the full annotation set and is worth bookmarking if you work heavily with JPA.

5. @Transactional: managing database consistency

If you want examples of practical examples of Java annotations that literally protect your data, @Transactional is near the top of the list.

@Service
public class CheckoutService {

    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;

    public CheckoutService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
    }

    @Transactional
    public void checkout(CheckoutRequest request) {
        Order order = createPendingOrder(request);
        orderRepository.save(order);

        paymentGateway.charge(order.getTotalAmount());

        order.markPaid();
        orderRepository.save(order);
    }
}

With @Transactional on the method, Spring opens a transaction before execution and commits or rolls back based on success or failure. This is one of the best examples where a single annotation encodes a serious policy: “either all these operations succeed, or none of them do.”

If you’ve ever debugged partial writes in a payment flow, you know how valuable that is.

6. Lombok annotations: @Getter, @Builder, @Value

Lombok isn’t part of standard Java, but in 2024 it still shows up in a ton of codebases. It gives some controversial but undeniably practical examples of Java-like annotations that generate boilerplate.

import lombok.Builder;
import lombok.Value;

@Value
@Builder
public class Notification {
    String recipientEmail;
    String subject;
    String body;
}

The @Value annotation makes the class immutable and generates:

  • All-args constructor
  • Getters
  • equals, hashCode, and toString

@Builder adds a fluent builder API:

Notification notification = Notification.builder()
        .recipientEmail("user@example.com")
        .subject("Welcome")
        .body("Thanks for signing up!")
        .build();

This is a strong example of how annotations can trade a bit of magic for much cleaner code, as long as your team agrees on the tooling.

7. Custom annotation for cross-cutting concerns

Frameworks give you a lot out-of-the-box, but some of the best examples of practical examples of Java annotations are the ones you design yourself. A classic case is logging or metrics.

First, define a custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimedOperation {
    String value() default "";
}

Then, use Spring AOP to intercept methods annotated with it:

@Aspect
@Component
public class TimingAspect {

    private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);

    @Around("@annotation(timedOperation)")
    public Object time(ProceedingJoinPoint pjp, TimedOperation timedOperation) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            long durationMs = (System.nanoTime() - start) / 1_000_000;
            String name = timedOperation.value().isBlank()
                    ? pjp.getSignature().toShortString()
                    : timedOperation.value();
            log.info("Operation {} took {} ms", name, durationMs);
        }
    }
}

And annotate methods you care about:

@Service
public class ReportService {

    @TimedOperation("monthlyReportGeneration")
    public Report generateMonthlyReport() {
        // heavy work here
    }
}

This pattern is one of the clearest real examples of annotations enabling aspect-oriented programming: you declare what should be timed, and the aspect handles how.

8. Testing annotations: JUnit 5 (@Test, @ParameterizedTest)

Testing frameworks provide another set of examples of practical examples of Java annotations that radically simplify configuration.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class PasswordValidatorTest {

    private final PasswordValidator validator = new PasswordValidator();

    @Test
    void validPassword_passesValidation() {
        assertTrue(validator.isValid("VeryStrongPassword123!"));
    }

    @ParameterizedTest
    @ValueSource(strings = {"short", "1234567", "noDigitsHere"})
    void invalidPasswords_failValidation(String candidate) {
        assertFalse(validator.isValid(candidate));
    }
}

Here, annotations tell JUnit what to run and how:

  • @Test marks a standard test method.
  • @ParameterizedTest and @ValueSource create a data-driven test.

The JUnit 5 user guide (https://junit.org/junit5/docs/current/user-guide/) is a solid reference for all the annotation-driven features.

9. Modern configuration with @ConfigurationProperties

In modern Spring Boot apps (especially 2024-era microservices), strongly typed configuration is a big deal. @ConfigurationProperties is a neat example of using annotations to keep config safe and discoverable.

@ConfigurationProperties(prefix = "app.rate-limiting")
public record RateLimitingProperties(
        int requestsPerMinute,
        int burstCapacity
) {}

@Configuration
@EnableConfigurationProperties(RateLimitingProperties.class)
public class RateLimitingConfig {

    private final RateLimitingProperties properties;

    public RateLimitingConfig(RateLimitingProperties properties) {
        this.properties = properties;
    }

    @Bean
    public RateLimiter rateLimiter() {
        return new RateLimiter(properties.requestsPerMinute(), properties.burstCapacity());
    }
}

This is one of the best examples of practical examples of Java annotations where type safety meets operational concerns. Your YAML or properties file is checked against a Java type instead of being a loose bag of strings.

Why these examples still matter in 2024–2025

You might think annotations are old news, but the way we use them keeps evolving:

  • Records and pattern matching (Java 17+) pair nicely with validation annotations, making DTOs concise and safer.
  • Cloud-native frameworks like Spring Boot 3 and Quarkus lean heavily on annotations for configuration, security, and observability.
  • Static analysis tools (like SpotBugs or NullAway) use annotations such as @Nullable and @CheckReturnValue to catch bugs before runtime.

Across all these, the strongest real examples share a theme: annotations express intent in a compact way, and tools or frameworks do the heavy lifting.

FAQ: short answers with focused examples

What are some common examples of Java annotations used in enterprise apps?

Common examples include @Override, @Deprecated, JPA annotations like @Entity and @Id, Spring annotations such as @RestController, @Service, @Transactional, validation annotations like @NotNull and @Email, and testing annotations like @Test and @ParameterizedTest in JUnit 5.

Can you give an example of a custom Java annotation and when to use it?

A practical example of a custom annotation is @TimedOperation for measuring method execution time in a service layer. You annotate selected methods and use an aspect (@Aspect) to log duration, instead of sprinkling timing code throughout your business logic.

Are annotations required for modern Java development?

They’re not mandatory, but almost every modern Java stack uses them heavily. Frameworks such as Spring, Jakarta EE, and JUnit rely on annotations to configure behavior, wire dependencies, and define APIs. Ignoring them means ignoring the best examples of practical examples of Java annotations that your tools already support.

Where can I learn more about the specification behind Java annotations?

The Java Language Specification has a dedicated section on annotations, available from Oracle: https://docs.oracle.com/javase/specs/. For framework-specific usage, the Jakarta EE specs (like Bean Validation and Persistence) and the JUnit 5 user guide provide detailed, annotation-focused documentation.

Wrapping up

If you remember nothing else, remember this: the best examples of practical examples of Java annotations all have the same property. They replace repetitive, scattered code with a clear, declarative marker that tools understand. Whether it’s @Transactional guarding your data, @NotNull guarding your APIs, or a custom @TimedOperation guarding your performance, annotations are at their best when they encode policy, not trivia.

Use the real examples above as a checklist against your own codebase. Anywhere you see repeated patterns, there’s a good chance an annotation—framework-provided or custom—can turn that pattern into a single, readable declaration.

Explore More Java Code Snippets

Discover more examples and insights in this category.

View All Java Code Snippets