Java Tutorial

Lesson 5: Java @ Annotations

All major Java @ use cases: built-ins, custom annotations, meta-annotations, repeatable annotations, type-use annotations, package and record targets, decorator-style metadata, getter/setter metadata, and reflection.

Run

scripts/run.sh lesson_5 demo
scripts/run.sh lesson_5 exercise
scripts/apply_answers.sh lesson_5
scripts/run.sh lesson_5 verify
scripts/reset_exercises.sh lesson_5

Function Pages

106 pages generated for Java functions in this lesson.

View related functions

Lesson README

Lesson 5: Java @ Annotations

This lesson explains what the @ symbol means in Java and shows the major places it appears: built-in annotations, custom annotations, meta-annotations, repeatable annotations, type-use annotations, package annotations, record component annotations, type-parameter annotations, decorator/interceptor-style annotations, getter/setter-style annotations, reflection, and @interface annotation declarations.

120-Minute Plan

MinutesActivity
0-15Run the demo and list every visible @ usage.
15-35Read built-in annotations: @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, and @FunctionalInterface.
35-55Read custom annotations and meta-annotations: @interface, @Retention, @Target, @Documented, @Inherited, and @Repeatable.
55-75Trace runtime reflection over classes, methods, parameters, annotated types, packages, record components, and type parameters.
75-105Implement the annotation reflection exercise in AnnotationExplorer.
105-115Apply answers and run the verifier.
115-120Discuss when annotations are useful and when explicit code is clearer.

Exercise

Use workbook.md for the full two-hour learner path. It includes annotation-reading drills, implementation rounds, and edge-case checks.

Edit src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java.

Functions to implement:

The lesson compiles before implementation. After applying answers, run scripts/run.sh lesson_5 verify to check reflection, repeatable annotations, inherited annotations, type-use annotations, deprecated metadata, decorator-style metadata, getter/setter-style metadata, package annotations, record component annotations, and type-parameter annotations.

Learner Workbook

Learner Workbook: Lesson 5 Java @ Annotations

This workbook is designed to take about two hours. The goal is to understand what Java annotations are, how annotation declarations work, where annotations can be placed, and how runtime reflection can read them.

What You Should Understand By The End

0-15 Minutes: Run And Inventory

Run:

scripts/reset_exercises.sh lesson_5
scripts/run.sh lesson_5 demo
scripts/run.sh lesson_5 exercise

Before opening the source, write down every @ name printed or implied by the output. Group them into:

15-35 Minutes: Built-In Annotation Tour

Open:

Find these examples:

For each one, answer: is this used by the compiler, tools, runtime reflection, or some combination?

35-55 Minutes: Custom Annotation Declarations

Open:

Study the @interface syntax. Annotation methods such as String path(); are not normal methods with bodies; they declare annotation elements.

Focus on these meta-annotations:

55-75 Minutes: Reflection Trace

Open AnnotationUseDemo.java.

Trace these reflection calls:

Answer these questions:

75-105 Minutes: Implement In Rounds

Edit src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java.

Round 1:

Round 2:

Round 3:

Round 4:

Round 5:

Round 6:

Round 7:

Round 8:

Round 9:

Round 10:

Round 11:

105-115 Minutes: Verify Answers

Run:

scripts/apply_answers.sh lesson_5
scripts/run.sh lesson_5 verify
scripts/reset_exercises.sh lesson_5

The verifier checks missing methods, missing annotations, repeatable annotations, inherited annotations, type-use annotations, non-deprecated methods, decorator metadata, getter/setter metadata, package annotations, record component annotations, and type-parameter annotations.

115-120 Minutes: Annotation Design Drill

Design one annotation for a real project you know. Decide:

Instructor Notes

Instructor Notes: Lesson 5 Java @ Annotations

Session Goal

Teach annotations as metadata attached to Java declarations and types. Learners should understand common built-in annotations, how custom annotations are declared, how meta-annotations constrain them, how frameworks use reflection to read them, and why getter/setter annotations usually need annotation processors.

Learning Outcomes

By the end of 120 minutes, learners should be able to:

Before Class

Run:

scripts/reset_exercises.sh lesson_5
scripts/run.sh lesson_5 demo
scripts/run.sh lesson_5 exercise

Know the key files:

Teaching Flow

TimeSegmentInstructor Moves
0-15Demo runRun the demo, then ask learners to classify every @ they saw.
15-35Built-insExplain annotations checked by the compiler versus annotations read at runtime.
35-55Annotation declarationsOpen @interface files and explain annotation elements and meta-annotations.
55-75ReflectionTrace how class, method, repeatable, inherited, and type-use annotations are read.
75-105ExerciseLearners implement AnnotationExplorer in staged rounds.
105-115VerifyApply answers and run scripts/run.sh lesson_5 verify.
115-120Design discussionAsk when annotations are useful and when explicit code is better.

Teaching Script

Use this framing:

"The @ symbol does not run code by itself. It attaches metadata. Something else must care: the compiler, Javadoc, a framework, an annotation processor, or runtime reflection."

Important distinctions:

Exercise Guidance

Learners edit AnnotationExplorer.java.

Give hints in this order:

Common Mistakes

Functions In This Lesson

AnnotationExplorer.annotationSimpleNames

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:15 | method | answer

public static List<String> annotationSimpleNames(Class<?> type) {

AnnotationExplorer.endpointSummary

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:22 | method | answer

public static String endpointSummary(Class<?> type, String methodName) {

AnnotationExplorer.permissionValues

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:29 | method | answer

public static List<String> permissionValues(Class<?> type) {

AnnotationExplorer.inheritedRuntimeCheck

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:36 | method | answer

public static Optional<String> inheritedRuntimeCheck(Class<?> type) {

AnnotationExplorer.deprecatedSummary

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:49 | method | answer

public static String deprecatedSummary(Method method) {

AnnotationExplorer.decoratorName

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:57 | method | answer

public static Optional<String> decoratorName(Class<?> type, String methodName) {

AnnotationExplorer.hasGetterSetterMetadata

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:63 | method | answer

public static boolean hasGetterSetterMetadata(Class<?> type, String fieldName) {

AnnotationExplorer.packageMarkerValue

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:74 | method | answer

public static Optional<String> packageMarkerValue(Package targetPackage) {

AnnotationExplorer.recordComponentColumn

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:79 | method | answer

public static Optional<String> recordComponentColumn(Class<?> recordType, String componentName) {

AnnotationExplorer.typeParameterTags

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:91 | method | answer

public static List<String> typeParameterTags(Class<?> type) {

AnnotationExplorer.findMethod

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:99 | method | answer

private static Optional<Method> findMethod(Class<?> type, String methodName) {

AnnotationExplorer.typeHintValues

lesson_5/answers/java/tutorial/lesson5/exercise/AnnotationExplorer.java:105 | method | answer

private static List<String> typeHintValues(Parameter parameter) {

Account.Account

lesson_5/src/demo/java/tutorial/lesson5/demo/Account.java:8 | constructor | demo

public Account(String displayName) {

Account.displayName

lesson_5/src/demo/java/tutorial/lesson5/demo/Account.java:12 | method | demo

public String displayName() {

Account.changeDisplayName

lesson_5/src/demo/java/tutorial/lesson5/demo/Account.java:16 | method | demo

public void changeDisplayName(String displayName) {

AnnotationUseDemo.main

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUseDemo.java:18 | method | demo

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

AnnotationUseDemo.runHandler

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUseDemo.java:41 | method | demo

private static String runHandler(RequestHandler handler, String name) {

AnnotationUseDemo.printTypeUseAnnotation

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUseDemo.java:78 | method | demo

private static void printTypeUseAnnotation() throws ReflectiveOperationException {

AnnotationUseDemo.printConstructorProperties

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUseDemo.java:119 | method | demo

private static void printConstructorProperties() throws ReflectiveOperationException {

AnnotationUtilities.flatten

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUtilities.java:11 | method | demo

public static <T> List<T> flatten(List<T>... lists) {

AnnotationUtilities.rawListDemo

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUtilities.java:20 | method | demo

public static List<String> rawListDemo() {

AnnotationUtilities.oldEndpoint

lesson_5/src/demo/java/tutorial/lesson5/demo/AnnotationUtilities.java:27 | method | demo

public static String oldEndpoint() {

ApiController.createUser

lesson_5/src/demo/java/tutorial/lesson5/demo/ApiController.java:9 | method | demo

public String createUser(@NonBlank String name) {

Audited.value

lesson_5/src/demo/java/tutorial/lesson5/demo/Audited.java:13 | interface method | demo

String value();

BeanPoint.BeanPoint

lesson_5/src/demo/java/tutorial/lesson5/demo/BeanPoint.java:10 | constructor | demo

public BeanPoint(int x, int y) {

BeanPoint.x

lesson_5/src/demo/java/tutorial/lesson5/demo/BeanPoint.java:15 | method | demo

public int x() {

BeanPoint.y

lesson_5/src/demo/java/tutorial/lesson5/demo/BeanPoint.java:19 | method | demo

public int y() {

BillingService.charge

lesson_5/src/demo/java/tutorial/lesson5/demo/BillingService.java:5 | method | demo

public String charge(String accountId, int cents) {

JsonHandler.handle

lesson_5/src/demo/java/tutorial/lesson5/demo/JsonHandler.java:5 | method | demo

public String handle(String name) {

LocalOnly.value

lesson_5/src/demo/java/tutorial/lesson5/demo/LocalOnly.java:11 | interface method | demo

String value();

PackageMarker.value

lesson_5/src/demo/java/tutorial/lesson5/demo/PackageMarker.java:11 | interface method | demo

String value();

RecordKey.value

lesson_5/src/demo/java/tutorial/lesson5/demo/RecordKey.java:11 | interface method | demo

String value();

RequestHandler.handle

lesson_5/src/demo/java/tutorial/lesson5/demo/RequestHandler.java:5 | interface method | demo

String handle(String name);

Role.value

lesson_5/src/demo/java/tutorial/lesson5/demo/Role.java:13 | interface method | demo

String value();

Roles.value

lesson_5/src/demo/java/tutorial/lesson5/demo/Roles.java:11 | interface method | demo

Role[] value();

Route.method

lesson_5/src/demo/java/tutorial/lesson5/demo/Route.java:13 | interface method | demo

String method();

Route.path

lesson_5/src/demo/java/tutorial/lesson5/demo/Route.java:15 | interface method | demo

String path();

SerializableToken.readResolve

lesson_5/src/demo/java/tutorial/lesson5/demo/SerializableToken.java:17 | method | demo

private Object readResolve() {

SerializableToken.value

lesson_5/src/demo/java/tutorial/lesson5/demo/SerializableToken.java:21 | method | demo

public String value() {

TypedRepository.TypedRepository

lesson_5/src/demo/java/tutorial/lesson5/demo/TypedRepository.java:6 | constructor | demo

public TypedRepository(Class<T> entityType) {

TypedRepository.entityType

lesson_5/src/demo/java/tutorial/lesson5/demo/TypedRepository.java:10 | method | demo

public Class<T> entityType() {

TypeParameterRole.value

lesson_5/src/demo/java/tutorial/lesson5/demo/TypeParameterRole.java:11 | interface method | demo

String value();

AnnotationExplorer.annotationSimpleNames

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:11 | method | exercise

public static List<String> annotationSimpleNames(Class<?> type) {

AnnotationExplorer.endpointSummary

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:17 | method | exercise

public static String endpointSummary(Class<?> type, String methodName) {

AnnotationExplorer.permissionValues

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:23 | method | exercise

public static List<String> permissionValues(Class<?> type) {

AnnotationExplorer.inheritedRuntimeCheck

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:28 | method | exercise

public static Optional<String> inheritedRuntimeCheck(Class<?> type) {

AnnotationExplorer.typeUseHintsOnFirstParameter

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:33 | method | exercise

public static List<String> typeUseHintsOnFirstParameter(Class<?> type, String methodName) {

AnnotationExplorer.deprecatedSummary

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:39 | method | exercise

public static String deprecatedSummary(Method method) {

AnnotationExplorer.decoratorName

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:45 | method | exercise

public static Optional<String> decoratorName(Class<?> type, String methodName) {

AnnotationExplorer.hasGetterSetterMetadata

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:51 | method | exercise

public static boolean hasGetterSetterMetadata(Class<?> type, String fieldName) {

AnnotationExplorer.packageMarkerValue

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:57 | method | exercise

public static Optional<String> packageMarkerValue(Package targetPackage) {

AnnotationExplorer.recordComponentColumn

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:62 | method | exercise

public static Optional<String> recordComponentColumn(Class<?> recordType, String componentName) {

AnnotationExplorer.typeParameterTags

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnnotationExplorer.java:67 | method | exercise

public static List<String> typeParameterTags(Class<?> type) {

AnswerVerifier.AnswerVerifier

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnswerVerifier.java:7 | constructor | exercise

private AnswerVerifier() {

AnswerVerifier.main

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnswerVerifier.java:10 | method | exercise

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

AnswerVerifier.expectEquals

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AnswerVerifier.java:66 | method | exercise

private static void expectEquals(Object expected, Object actual, String label) {

AuditService.save

lesson_5/src/exercise/java/tutorial/lesson5/exercise/AuditService.java:5 | method | exercise

public String save(String value) {

Decorates.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Decorates.java:11 | interface method | exercise

String value();

Endpoint.method

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Endpoint.java:11 | interface method | exercise

String method();

Endpoint.path

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Endpoint.java:13 | interface method | exercise

String path();

ExerciseRunner.ExerciseRunner

lesson_5/src/exercise/java/tutorial/lesson5/exercise/ExerciseRunner.java:4 | constructor | exercise

private ExerciseRunner() {

ExerciseRunner.main

lesson_5/src/exercise/java/tutorial/lesson5/exercise/ExerciseRunner.java:7 | method | exercise

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

LegacyApi.LegacyApi

lesson_5/src/exercise/java/tutorial/lesson5/exercise/LegacyApi.java:4 | constructor | exercise

private LegacyApi() {

LegacyApi.oldName

lesson_5/src/exercise/java/tutorial/lesson5/exercise/LegacyApi.java:8 | method | exercise

public static String oldName() {

PackageMarker.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/PackageMarker.java:11 | interface method | exercise

String value();

Permission.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Permission.java:13 | interface method | exercise

String value();

Permissions.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Permissions.java:11 | interface method | exercise

Permission[] value();

Profile.Profile

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Profile.java:8 | constructor | exercise

public Profile(String nickname) {

Profile.nickname

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Profile.java:12 | method | exercise

public String nickname() {

Profile.changeNickname

lesson_5/src/exercise/java/tutorial/lesson5/exercise/Profile.java:16 | method | exercise

public void changeNickname(String nickname) {

RecordColumn.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/RecordColumn.java:11 | interface method | exercise

String value();

RuntimeCheck.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/RuntimeCheck.java:13 | interface method | exercise

String value();

TypeHint.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/TypeHint.java:11 | interface method | exercise

String value();

TypeParameterTag.value

lesson_5/src/exercise/java/tutorial/lesson5/exercise/TypeParameterTag.java:11 | interface method | exercise

String value();

UserService.findUser

lesson_5/src/exercise/java/tutorial/lesson5/exercise/UserService.java:8 | method | exercise

public String findUser(@TypeHint("external-id") String id) {

AnnotationExplorer.endpointSummary

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:17 | method | stub

public static String endpointSummary(Class<?> type, String methodName) {

AnnotationExplorer.permissionValues

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:23 | method | stub

public static List<String> permissionValues(Class<?> type) {

AnnotationExplorer.inheritedRuntimeCheck

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:28 | method | stub

public static Optional<String> inheritedRuntimeCheck(Class<?> type) {

AnnotationExplorer.deprecatedSummary

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:39 | method | stub

public static String deprecatedSummary(Method method) {

AnnotationExplorer.decoratorName

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:45 | method | stub

public static Optional<String> decoratorName(Class<?> type, String methodName) {

AnnotationExplorer.hasGetterSetterMetadata

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:51 | method | stub

public static boolean hasGetterSetterMetadata(Class<?> type, String fieldName) {

AnnotationExplorer.packageMarkerValue

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:57 | method | stub

public static Optional<String> packageMarkerValue(Package targetPackage) {

AnnotationExplorer.recordComponentColumn

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:62 | method | stub

public static Optional<String> recordComponentColumn(Class<?> recordType, String componentName) {

AnnotationExplorer.typeParameterTags

lesson_5/stubs/java/tutorial/lesson5/exercise/AnnotationExplorer.java:67 | method | stub

public static List<String> typeParameterTags(Class<?> type) {