W3docs

Java Reflection: Inspecting Fields

Inspect, read, and modify fields at runtime in Java with the reflection API.

Java Reflection: Inspecting Fields

A Field object describes one field of a class: its name, its type, its modifiers, and — given an instance — its value. Reflection lets you list a class's fields, read them, and write them, even when they're private and have no getter or setter. This is exactly how JSON deserializers populate objects and how ORMs hydrate entities. This chapter covers obtaining Field objects, reading and writing values, the setAccessible gate, and the special case of final fields.

Getting Field objects

The same public-vs-declared split from the intro applies:

Class<?> c = User.class;

Field f1 = c.getField("name");             // public field, incl. inherited — else NoSuchFieldException
Field f2 = c.getDeclaredField("name");     // any access level, this class only

Field[] all   = c.getFields();             // public fields, incl. inherited
Field[] mine  = c.getDeclaredFields();     // all access levels, this class only

getField/getFields only ever see public fields but follow the inheritance chain. getDeclaredField/getDeclaredFields see private/protected/package fields too, but only those literally declared on the class you ask. To collect every field including inherited private ones, walk getSuperclass() and merge.

Field metadata: name, type, modifiers, generics

A Field answers questions about itself without needing any instance:

Field f = User.class.getDeclaredField("age");

f.getName();           // "age"
f.getType();           // int.class           — the erased type
f.getGenericType();    // int                 — Type, keeps generic info
f.getModifiers();      // int bitset
Modifier.isPrivate(f.getModifiers());   // true/false
Modifier.isStatic(f.getModifiers());
Modifier.isFinal(f.getModifiers());
f.getDeclaringClass(); // class …User

getType() gives the erased Class (List); getGenericType() returns a Type that, for a List<String> field, you can downcast to ParameterizedType to recover String. That recovery works because generic field signatures are retained in the class file even though instances are erased.

Reading and writing values

To read or write, you need an instance (or null for a static field) and you must get past the access check:

User u = new User("ada", 36);
Field age = User.class.getDeclaredField("age");
age.setAccessible(true);               // bypass the access check for private

int current = age.getInt(u);           // typed getter for primitives → 36
age.setInt(u, 37);                     // typed setter
Object boxed = age.get(u);             // generic getter, autoboxes → Integer 37
age.set(u, 40);                        // generic setter, autounboxes

There are typed accessors — getInt, getBoolean, getDouble, setLong, … — for primitive fields, and the generic get(Object)/set(Object,Object) for any field (boxing primitives). For a static field, pass null as the target: staticField.get(null).

The setAccessible gate

By default a Field enforces Java's access rules: reading a private field reflectively throws IllegalAccessException. field.setAccessible(true) suppresses that check for this Field object. It's what makes reflection able to touch internals — and what makes it dangerous.

Two caveats since Java 9:

  • Module boundaries. If the target type is in a module that hasn't opens its package to you, setAccessible(true) throws InaccessibleObjectException. Libraries ask you to add --add-opens or the module to opens the package.
  • It's per-object. Calling setAccessible(true) affects only the Field instance you called it on, not the field globally. A freshly looked-up Field for the same member starts locked again.

Writing final fields

final fields are a special, fraught case. For a non-static final field you can sometimes still write it after setAccessible(true):

Field f = Config.class.getDeclaredField("name");   // private final String
f.setAccessible(true);
f.set(config, "changed");                            // may work…

But there are heavy caveats:

  • It does not work for static final primitive or String constants — those are inlined by the compiler at every use site, so even if you change the field the already-compiled reads won't reflect it.
  • The JVM and JIT assume final fields never change; mutating one is undefined behaviour for visibility and can be optimized away.
  • Modern JDKs increasingly forbid it outright.

The honest rule: don't mutate final fields reflectively in production. Serialization frameworks that do it (to reconstruct immutable objects) use low-level Unsafe/VarHandle machinery and accept the risk deliberately. The example below shows the instance-final case working to illustrate the mechanism, not as a recommendation.

A worked example: a tiny field-based mapper

The program reflects over an object's declared fields to build a Map<String,Object> (a mini serializer), then takes a map and writes its values back into a fresh instance (a mini deserializer) — touching private fields throughout, with no getters or setters anywhere.

java— editable, runs on the server

What to take from the run:

  • toMap produced a snapshot of every instance field without a single getter — getDeclaredFields() plus setAccessible(true) reached the private state directly. That is, mechanically, what Jackson and Gson do when configured for field access. The class needs no special API; reflection supplies the generic one.
  • The static count field was excluded because the loop tested Modifier.isStatic. Serializers routinely skip static, transient, and synthetic fields; the modifier bitset is how you make those decisions uniformly instead of hard-coding field names.
  • fromMap wrote the private final currency field after setAccessible(true) and it took effect — demonstrating the instance-final mechanism. This worked only because currency is a non-static final reassigned before any optimizer assumed it constant; relying on this in real code is fragile, and static final constants would not have budged.
  • Reading metadata (bal.getType(), Modifier.toString(...), isFinal(...)) needed no Account instance at all — a Field describes the declaration, which is the same for every object of the class. Values need an instance; shape does not.
  • The typed getInt(rebuilt) returned the primitive directly without boxing, and the static field read used cnt.get(null) — passing null as the target is the convention for statics. Picking the typed accessor for primitives avoids an allocation per read, which matters in hot serialization paths.

Practice

Practice

A JSON library deserializes into a class that has 'private' fields and no setters. Using reflection it calls 'getDeclaredField(name)' then 'field.set(obj, value)', but gets 'IllegalAccessException' on the first private field. Adding which single call fixes it, and why is it needed?