HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
4 Topics
Chapter•70 Minutes

Reflection, Annotations, Modules, and Metaprogramming

Understand reflection, annotations, dynamic proxies, modules, service loading, and the trade-offs of runtime metaprogramming.

Reflection

Reflection lets code inspect classes, methods, fields, constructors, and annotations at runtime.

Class<?> type = User.class;

for (var method : type.getDeclaredMethods()) {
    System.out.println(method.getName());
}

Frameworks use reflection for dependency injection, serialization, ORM mapping, testing, and plugin systems. Reflection can weaken compile-time guarantees and complicate encapsulation, performance analysis, and native-image compatibility.

Use reflection at framework boundaries rather than spreading it through ordinary business logic.

Reflection is a tool, not a design default

Prefer normal Java calls for business logic; use reflection when the framework truly needs dynamic discovery.

Key Takeaways
  • Reflection provides runtime type inspection and invocation.
  • It trades some compile-time safety for flexibility.
  • Framework infrastructure is a better home for reflection.

Annotations and Metadata

Annotations attach metadata to declarations. Frameworks can define custom annotations and inspect them at compile time or runtime.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audited {
    String action();
}

Retention controls whether metadata is available in source, class files, or runtime reflection. Annotation processing can generate code during compilation and avoid some runtime reflection.

Key Takeaways
  • Annotations are metadata, not executable behavior by themselves.
  • Retention determines how long annotation information is available.
  • Compile-time processing can move work out of runtime.

Dynamic Proxies and Service Loading

Java can create dynamic proxies around interfaces, enabling cross-cutting behavior such as logging, metrics, authorization, or transaction interception. Service loading provides a standard plugin discovery mechanism.

ServiceLoader<MyPlugin> plugins =
    ServiceLoader.load(MyPlugin.class);

for (MyPlugin plugin : plugins) {
    plugin.start();
}

These mechanisms are useful for extensibility but introduce indirection that should be documented.

Key Takeaways
  • Dynamic proxies are primarily interface-oriented.
  • Service loading supports pluggable implementations.
  • Runtime indirection should be documented.

JPMS Modules and Strong Encapsulation

A module declares dependencies and exported packages.

module com.example.orders {
    requires java.net.http;
    exports com.example.orders.api;
}

exports controls package access across modules. opens is relevant when reflective access to otherwise encapsulated packages is required.

Packages organize names; modules define a larger dependency and encapsulation boundary.

Key Takeaways
  • Packages and modules solve different levels of organization.
  • exports controls cross-module accessibility.
  • opens is relevant to reflective access.