How do you write code that automatically knows how to handle different types of objects, without you having to hardcode every possibility? That is the exact problem annotations and reflection solve, and it is a core skill for the 1Z0-829 exam. Mastering these two concepts will unlock your understanding of how modern Java frameworks like Spring and Hibernate work their magic.
Jump to a section
A simple way to picture Annotations and Reflection
Have you ever wondered how a librarian can instantly tell you the author, publication year, and whether a book has been borrowed recently, just by scanning a code on the back? That is exactly what annotations and reflection do in Java, but for pieces of code instead of books.
Think of a Java class (a blueprint for creating objects) as a book. On the inside cover, a librarian uses a stamp to place small labels: 'Reference Only,' 'New Arrival,' 'Award Winner.' Those stamps are your annotations — metadata attached directly to the book (or class) that anyone reading it can see. They do not change the story itself; they just tell you something extra about how to treat that book.
Now, reflection is the librarian's tool. It lets the librarian pick up any book, open it, and read all the stamps, the table of contents, every chapter heading, and even the index — all without knowing the book's title beforehand. In Java, reflection is code that examines other code at runtime: it can discover what methods a class has, what annotations are on it, and even call those methods. The stamps (annotations) are useless if nobody ever reads them. The librarian (reflection) is the one who reads them and decides, 'This is a Reference Only book, so I will not let it leave the library.' The combination of stamps and the librarian's ability to inspect them is what gives Java developers superpowers to write frameworks, tools, and very flexible programs.
Annotations and reflection feel like magic at first, but they are really just two simple ideas that work together. Let us break them down piece by piece.
First, what is an annotation? In Java, an annotation is a form of metadata. Metadata is just 'data about data,' which is a fancy way of saying 'information that describes other information.' An annotation on a class, method, or field does not change what that code does. It only tells the compiler or the runtime something extra. Think of it like a sticky note you put on a file folder. The sticky note does not change the papers inside, but it tells you (or a coworker) whether the folder is 'Urgent,' 'Confidential,' or 'Needs Review.'
Annotations always start with an @ symbol. A built-in annotation you have probably already seen is @Override. When you put @Override above a method, you are telling the compiler: 'I intend for this method to override a method in a parent class.' If you make a typo and do not actually override anything, the compiler will give you an error. That is the annotation doing its job — it is metadata that the compiler uses to validate your code.
Other built-in annotations you must know for the exam include:
@Deprecated: Marks a method or class as old and not recommended for use. The compiler will warn anyone who uses it.
@SuppressWarnings: Tells the compiler not to report specific warnings for that piece of code.
@FunctionalInterface: Declares that an interface is meant to be a functional interface (an interface with exactly one abstract method).
@Retention and @Target: These are called 'meta-annotations' because they annotate other annotations. @Retention tells Java how long to keep the annotation: SOURCE (discard it after compilation), CLASS (keep it in the .class file but ignore it at runtime), or RUNTIME (keep it available for reflection at runtime). @Target specifies what kinds of Java elements the annotation can be placed on: methods, fields, classes, parameters, and so on.
Now, reflection. Reflection is the ability of a running Java program to examine itself. It is like the program looking in a mirror and describing what it sees. With reflection, you can do things like:
Find out what class an object belongs to.
List all the methods a class has, including their names, return types, and parameters.
List all the fields (variables) of a class, including their names and types.
Inspect annotations present on a class, method, or field.
Invoke methods or access fields dynamically, even if you only know their name as a string at runtime.
The core class for reflection is java.lang.Class. Every object in Java has a .getClass() method that returns its Class object. From there, you can call methods like .getDeclaredMethods(), .getDeclaredFields(), .getAnnotations(), and .getMethod("methodName", parameterTypes).
Why does this matter? Without reflection, if you wanted to write a tool that could handle any kind of object, you would have to write code for every single possible class — an impossible task. Reflection lets you write generic code that inspects whatever object is handed to it and reacts accordingly. For example, a JSON serialisation library can take any object, reflect on its fields and annotations, and automatically convert it to JSON.
Annotations and reflection are often used together. You define a custom annotation with @Retention(RetentionPolicy.RUNTIME) so it stays available at runtime. Then you use reflection to scan a class for that annotation. When you find it, you execute special logic. This is the foundation of virtually all modern Java frameworks.
One critical thing to remember: reflection is powerful but slow and can break encapsulation (it can access private fields). Use it only when necessary. The exam will test that you understand these trade-offs.
Define a Custom Annotation
Create an annotation type using @interface. Choose the retention policy (usually RUNTIME for reflection to see it) and target (e.g., METHOD, FIELD, TYPE). This is the stamp you will later look for.
Decorate Code with the Annotation
Place the annotation on the class, method, or field you intend to process. Provide values for any annotation elements that lack defaults. This attaches the metadata to the code element.
Access the Class Object
Use the .class literal (e.g., MyClass.class) or an object's .getClass() method to obtain the Class object representing the type you want to inspect. This is the starting point for all reflection operations.
Retrieve Annotations from the Element
Call methods like .getAnnotations(), .getDeclaredAnnotations(), or .isAnnotationPresent(MyAnnotation.class) on the Class, Method, or Field object. This reads the metadata you attached.
Act on the Annotation Presence
Write conditional logic that checks whether the specific annotation is present. If it is, execute your custom behaviour—for example, calling a method, modifying a field, or generating a report.
Imagine you work for a company that builds web applications using Spring Boot, one of the most popular Java frameworks. Your team creates REST APIs that handle HTTP requests. Every API endpoint is a method in a Java class. You want a clean way to tell the framework: 'This method handles a GET request at the URL /users' and 'That method handles a POST request at /users/create.'
Using annotations, you simply put @GetMapping("/users") above the method. You put @PostMapping("/users/create") above another method. These annotations do not contain any logic themselves; they are just labels. Then Spring, at startup, uses reflection. It scans all your classes, finds every method annotated with @GetMapping or @PostMapping, reads the URL pattern from the annotation, and builds an internal mapping table. When a real HTTP request arrives at the server, Spring looks up the URL in that table and calls the correct method automatically.
Another real-world example involves serialisation libraries like Jackson. You might have a Java class called Customer with fields like firstName, lastName, and secretToken. You do not want secretToken to be sent out in a JSON response because it is sensitive. So you annotate that field with @JsonIgnore. Jackson, using reflection, sees that annotation and skips that field when converting the object to JSON.
A step-by-step walkthrough of what an IT professional does:
The developer writes a class with fields and annotates them (e.g., @NotNull, @Size(min=2)).
The developer builds the project and runs tests.
The testing framework (like JUnit) uses reflection to inspect the test class. It finds methods annotated with @Test.
For each @Test method, the framework creates an instance of the test class and invokes the method.
If a method is annotated with @BeforeEach, the framework runs it before every @Test method.
This all happens without the developer writing any manual 'if-this-then-that' code — the annotations and reflection handle the flow.
In daily work, you spend more time using annotations defined by frameworks than writing your own. But when you do need to build a custom tool — say, a report generator that finds all fields annotated with @Reportable in your entities — you will write your own annotation, give it RUNTIME retention, and use reflection to scan and process those fields. The exam expects you to know how to do both: use built-in annotations and write simple custom ones.
The 1Z0-829 exam tests annotations and reflection in a very specific way. You will not be asked to write a large reflection program. Instead, you will face multiple-choice and multiple-select questions that test your understanding of definitions, syntax, and common pitfalls. Here is exactly what you need to know.
First, memorise the built-in annotations that are explicitly in the exam objectives. The most important ones are:
@Override: Must be used when overriding a method. If you use it incorrectly, the code will not compile.
@Deprecated: Marks an API as obsolete. Calling a deprecated method will produce a compiler warning but not an error.
@SuppressWarnings: Takes a parameter like "unchecked" or "deprecation" and suppresses those warnings.
@SafeVarargs: Used on methods and constructors with varargs parameters to suppress warnings about heap pollution.
@FunctionalInterface: Compile-time check that an interface has only one abstract method.
@Retention, @Target, @Documented, @Inherited: These are meta-annotations. Know what each does. The exam loves to ask: which retention policy lets you read an annotation via reflection? Answer: RUNTIME.
For custom annotations, know the syntax. An annotation definition looks like an interface with an @ symbol:
public @interface MyAnnotation { String value() default ""; int count() default 0; }
Key exam traps:
Annotations can only have primitives, String, Class, enum, annotation types, or arrays of those as element types. Storing an Object in an annotation will not compile.
Elements can have default values. If you do not provide a default, you must supply a value when using the annotation.
If an annotation has a single element named 'value', you can omit the element name when using it. For example, @SuppressWarnings("unchecked") instead of @SuppressWarnings(value = "unchecked").
Reflection questions often test the Class class and its methods. Know the difference between:
.getDeclaredFields() vs .getFields(): The first returns all fields declared in the class (including private ones), but not inherited fields. The second returns only public fields, including inherited ones.
.getDeclaredMethods() vs .getMethods(): Analogous distinction.
.getAnnotations() returns all annotations present on this element, including inherited ones if the annotation is marked @Inherited.
.isAnnotationPresent(Class) is a quick way to check for a specific annotation.
AccessibleObject.setAccessible(true): This allows you to access private fields and methods via reflection. The exam may test whether you know this is possible, not just for public members.
A common question pattern: You are given a code snippet that uses reflection to invoke a private method. The code does not call setAccessible(true). The exam asks: will it compile? Will it run? Answer: It will compile, but it will throw an IllegalAccessException at runtime.
Another trap: mixing up @Retention values. If an annotation has @Retention(SOURCE), reflection cannot see it at runtime. If you try to find it with getAnnotations(), you will get nothing.
Study these patterns. Practice with small code examples. The exam is not about memorising every method in the reflection API — it is about understanding the core concepts and being able to predict the behaviour of code that uses annotations and reflection.
An annotation is metadata attached to code that does not change the code's behaviour on its own.
Reflection allows a running Java program to inspect and manipulate itself at runtime.
To read a custom annotation via reflection, it must have @Retention(RetentionPolicy.RUNTIME).
The Class class provides getDeclaredFields(), getMethods(), getAnnotations(), and similar methods to introspect a type.
Using setAccessible(true) on a Field or Method object lets reflection bypass private access modifiers.
Annotation element types are limited to primitives, String, Class, enums, annotations, and arrays of those.
@FunctionalInterface is a compile-time check ensuring an interface has exactly one abstract method.
The @Override annotation causes a compile error if the annotated method does not actually override a superclass method.
Reflection is powerful but slower than direct code and can break encapsulation, so use it sparingly.
Modern Java frameworks like Spring rely on runtime annotations and reflection to wire together applications automatically.
These come up on the exam all the time. Here's how to tell them apart.
Annotations
are metadata that can be processed by the compiler or runtime
can influence compilation (e.g., @Override causes errors if misused)
are defined with a strict syntax using @ symbols and elements
Comments
are ignored entirely by the compiler and runtime
exist only for human readability
have no syntax restrictions beyond // or /* */
Reflection
works at runtime and can inspect any class without prior knowledge
can access private fields and methods (with setAccessible)
is slower because it requires method lookup and security checks
Direct Code Access
requires you to know the class and method at compile time
cannot access private members from another class without helper methods
is much faster because method calls are resolved at compile time
getDeclaredFields()
returns all fields declared in this class (private, protected, package, public)
does not include inherited fields
is useful when you need to see internal structure of a specific class
getFields()
returns only public fields
includes public fields inherited from superclasses
is useful when you only care about the public API
RetentionPolicy.RUNTIME
annotation is recorded in the class file and retained by the JVM at runtime
can be read via reflection using getAnnotations()
required for runtime processing like in Spring or JUnit
RetentionPolicy.CLASS
annotation is recorded in the class file but discarded by the JVM
cannot be read via reflection at runtime
used for compile-time or bytecode processing tools
@Target(METHOD)
restricts the annotation to methods only
using the annotation on a class will cause a compile error
example use: marking a method as a test
@Target(TYPE)
restricts the annotation to classes, interfaces, or enums
using the annotation on a method will cause a compile error
example use: marking a class as deprecated
Mistake
Annotations change the behaviour of the code they annotate automatically.
Correct
Annotations are just metadata. They do nothing on their own. A framework or code that uses reflection must read the annotation and then decide to act on it.
New learners see annotations like @Override causing a compile error and assume all annotations change behaviour. They miss the fact that @Override works because the compiler specifically looks for it — not because annotations are magical.
Mistake
Reflection can only access public members of a class.
Correct
Reflection can access private fields and methods if you call setAccessible(true) on the Field or Method object. Without that call, private members are inaccessible and throw IllegalAccessException.
People confuse the normal Java access control rules (where you cannot call a private method from another class) with the reflection API rules. Reflection explicitly bypasses normal access, but only if you allow it.
Mistake
If I create a custom annotation and put it on a method, the method will automatically be called when my program runs.
Correct
An annotation does not execute any code. You must write a separate piece of code (usually using reflection) that scans for your annotation and then calls the annotated method or performs some action.
This comes from watching how frameworks like Spring work — it seems like methods with @GetMapping just handle web requests magically. Beginners do not see the massive reflection-based framework code running behind the scenes.
Mistake
You can put any type of element in an annotation, including objects like List or HashMap.
Correct
Annotation elements can only be primitives, String, Class, an enum, another annotation type, or an array of any of these. You cannot store a generic Object or a collection.
This rule is not obvious. Beginners try to store a List parameter in an annotation and are confused by the compilation error. The Java language specification restricts annotation types to ensure they can be efficiently processed at compile time and runtime.
Mistake
The @Inherited meta-annotation makes an annotation work on subclasses automatically for any element type.
Correct
@Inherited only works when you place the annotation on a class, and it causes the annotation to be inherited by subclasses. It does not work on methods, fields, or other elements. If you put an annotation on a method in a parent class, the child's override does not inherit that annotation.
People read '@Inherited' and assume it applies universally. The Java documentation is clear, but few beginners actually read it carefully. The exam loves to test this nuance.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
It depends on the annotation's retention policy. RUNTIME annotations are visible during program execution via reflection. SOURCE and CLASS retention annotations are not visible at runtime.
Yes, you can. Use Class.getDeclaredField() to get the Field object, then call field.setAccessible(true) to override the access controls. Without setAccessible(true), accessing a private field will throw IllegalAccessException.
getMethods() returns only public methods, including those inherited from superclasses. getDeclaredMethods() returns all methods declared in the class itself (including private and protected) but not inherited methods.
No. Annotation element types are restricted to primitives, String, Class, enums, other annotations, and arrays of those. A List is not allowed. You would have to use an array of String or another allowed type.
Most likely you forgot to set @Retention(RetentionPolicy.RUNTIME). Without it, the annotation is only kept in the source file or class file and is discarded by the JVM at runtime. Always add @Retention(RUNTIME) for reflection-based discovery.
Yes. @Override is a built-in annotation with SOURCE retention. It is used by the compiler to verify that the annotated method correctly overrides a method from a superclass. It is discarded after compilation.
It is a compile-time check that the annotated interface has exactly one abstract method. If the interface has zero or more than one abstract method, the compiler produces an error. It is informative, not required for the interface to be a functional interface.
You've finished Annotations and Reflection. Continue through the 1Z0-829 study guide to build a complete picture of the exam.
Done with this chapter?