Three features that frameworks rely on behind the scenes: serialization turns objects into bytes, reflection lets code inspect classes and call methods it only knows by name, and annotations attach metadata that frameworks read through reflection. TestNG, JUnit, Spring and Jackson all work this way.
Serialization & Deserialization
Making Objects Saveable
import java.io.*;
// Class must implement Serializable
public class Employee implements Serializable {
private static final long serialVersionUID = 1L; // version ID
String name;
int age;
transient String password; // NOT serialized (excluded)
static int count; // NOT serialized (class-level)
public Employee(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
}
// SERIALIZE (object → bytes → file)
Employee emp = new Employee("Alice", 30, "secret123");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("employee.ser"))) {
oos.writeObject(emp);
System.out.println("Serialized!");
}
// DESERIALIZE (file → bytes → object)
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("employee.ser"))) {
Employee loaded = (Employee) ois.readObject();
System.out.println(loaded.name); // Alice
System.out.println(loaded.age); // 30
System.out.println(loaded.password); // null (transient!)
}
serialVersionUID Deep Dive
// serialVersionUID = version fingerprint of the class
// If you serialize with UID = 1L, then change class, UID still 1L:
// → Deserialization works (compatible)
// If you DON'T declare serialVersionUID:
// JVM auto-generates based on class structure
// Any class change (add field, method) → different UID
// → InvalidClassException on deserialization!
// Always declare it explicitly:
private static final long serialVersionUID = 1L;
Interview Questions
What is serialVersionUID?
A unique identifier for a Serializable class. Used during deserialization to verify that the sender and receiver have compatible class definitions. If UIDs don't match, InvalidClassException is thrown. Always declare it explicitly to control compatibility.
What is the transient keyword?
Marks a field to be EXCLUDED from serialization. When deserializing, transient fields are set to their default values (null for objects, 0 for numbers, false for boolean). Used for sensitive data (passwords), computed fields, or non-serializable references.
Reflection API
Inspect & Manipulate Classes at Runtime
import java.lang.reflect.*;
// Get Class object (3 ways)
Class<?> c1 = String.class; // at compile time
Class<?> c2 = "hello".getClass(); // from instance
Class<?> c3 = Class.forName("java.lang.String"); // by name
// Inspect class info
System.out.println(c1.getName()); // java.lang.String
System.out.println(c1.getSimpleName()); // String
System.out.println(c1.getPackageName()); // java.lang
System.out.println(c1.getSuperclass().getSimpleName()); // Object
// Get fields
Field[] fields = c1.getDeclaredFields();
for (Field f : fields) {
System.out.println(f.getName() + " : " + f.getType());
}
// Get methods
Method[] methods = c1.getDeclaredMethods();
// Invoke method dynamically
Method lengthMethod = c1.getMethod("length");
int len = (int) lengthMethod.invoke("Hello World"); // 11
// Access private field
class Secret { private String code = "12345"; }
Secret s = new Secret();
Field codeField = Secret.class.getDeclaredField("code");
codeField.setAccessible(true); // bypass private!
String code = (String) codeField.get(s); // "12345"
// Create instance without calling constructor
Constructor<?> cons = c1.getConstructor(String.class);
String str = (String) cons.newInstance("Reflection!");
Interview Questions
What is Reflection and when to use it?
Reflection lets you inspect and manipulate classes, methods, and fields at RUNTIME, even private ones. Used in: frameworks (Spring, Hibernate), serialization, testing (Mockito), IDEs (autocomplete), annotation processing. Downsides: slower than direct calls, breaks encapsulation, can expose private APIs.
Custom Annotations
import java.lang.annotation.*;
// ── BUILT-IN ANNOTATIONS ──
@Override // check method actually overrides
@Deprecated // marks as outdated
@SuppressWarnings("unchecked") // suppress compiler warning
@FunctionalInterface // enforce SAM interface
@SafeVarargs // suppress generics varargs warning
// ── CREATING CUSTOM ANNOTATION ──
@Retention(RetentionPolicy.RUNTIME) // available at runtime via reflection
// RetentionPolicy.SOURCE: only in source, discarded by compiler
// RetentionPolicy.CLASS: in .class file, not at runtime (default)
// RetentionPolicy.RUNTIME: available at runtime via Reflection
@Target({ElementType.METHOD, ElementType.TYPE})
// ElementType.FIELD, METHOD, TYPE, PARAMETER, CONSTRUCTOR etc.
@Documented // include in Javadoc
@Inherited // subclass inherits this annotation
public @interface RateLimit {
int requestsPerMinute() default 60;
String message() default "Rate limit exceeded";
}
// ── USING CUSTOM ANNOTATION ──
@RateLimit(requestsPerMinute = 10, message = "Too many requests!")
public void sensitiveEndpoint() { }
// ── PROCESSING AT RUNTIME via Reflection ──
Method method = MyClass.class.getMethod("sensitiveEndpoint");
if (method.isAnnotationPresent(RateLimit.class)) {
RateLimit limit = method.getAnnotation(RateLimit.class);
System.out.println("Limit: " + limit.requestsPerMinute());
System.out.println("Message: " + limit.message());
}
// Real-world: Spring uses @Transactional, @Cacheable,
// @PreAuthorize — all custom annotations processed by AOP proxies