Backend Development 6 min read

Why Java Anonymous Inner Classes Require Final Variables and How Kotlin Handles Them Differently

The article explains why Java’s anonymous inner classes require captured variables to be final or effectively final, how this behavior changed after Java 8, and why Kotlin allows modification of such variables, illustrating the differences with decompiled code examples.

Top Architect
Top Architect
Top Architect
Why Java Anonymous Inner Classes Require Final Variables and How Kotlin Handles Them Differently

When developers habitually use a single programming language, they may overlook subtle differences that appear when multiple languages are involved. This article examines how Java and Kotlin treat variables captured by anonymous inner classes.

Before Java 8, attempting to reference a non‑final variable from an anonymous inner class produced a compile‑time error such as “Cannot refer to a non‑final variable arg inside an inner class defined in a different method”. After Java 8, the compiler no longer shows this warning, yet attempts to modify the captured primitive still result in an error like “Variable 'num' is accessed from within inner class, need to be final or effectively final”.

Kotlin, by contrast, imposes no such restriction, allowing direct modification of captured primitive values inside anonymous inner classes.

To understand the cause, we look at the class files generated by javac . The anonymous inner class is compiled into a separate .class file (e.g., TestInnerClass$1.class ) which, when decompiled, reveals the following structure:

class TestInnerClass$1 extends InnerClass {
    TestInnerClass$1(TestInnerClass var1, int var2, DataBean var3) {
        super(var1);
        this.this$0 = var1;
        this.val$num = var2;
        this.val$bean = var3;
    }
    void doSomething() {
        super.doSomething();
        System.out.println("num = " + this.val$num);
        System.out.println("bean name is: " + this.val$bean.name);
    }
}

The compiler treats the anonymous class like a normal class, copying primitive variables into fields and passing object references unchanged. Because the primitive field is a copy, modifying it would diverge from the original outer variable, so the language enforces the final (or effectively final) rule.

Kotlin’s compiler handles this differently. When a primitive needs to be captured, it wraps the value in a mutable reference object (e.g., IntRef ), turning value semantics into reference semantics. The decompiled Kotlin code illustrates this:

public final void useNestedClass(@NotNull final TestNestedClass.DataBean bean) {
    Intrinsics.checkParameterIsNotNull(bean, "bean");
    final IntRef num = new IntRef(); //---1
    num.element = 1; //---2
    String var3 = "before action, num = " + num.element;
    System.out.println(var3);
    TestNestedClass.NestedClass nestedClass = new TestNestedClass.NestedClass() {
        public void doSomething() {
            num.element = 678; //---3
            bean.setName("xyz");
            String var1 = "num = " + num.element;
            System.out.println(var1);
            var1 = "bean name is: " + bean.getName();
            System.out.println(var1);
        }
    };
    nestedClass.doSomething();
    String var4 = "after action, num = " + num.element; //---4
    System.out.println(var4);
}

By wrapping the primitive in IntRef , Kotlin allows the inner class to modify the value without violating the outer scope’s immutability contract. When no capture is needed, Kotlin generates straightforward Java‑like code that directly reassigns the primitive.

In summary, Java enforces final for captured variables to prevent inconsistencies between the outer and inner scopes, while Kotlin sidesteps this limitation by converting primitives to mutable reference objects, enabling more flexible anonymous inner class usage.

JavaCompilationKotlinAnonymous Inner Classfinallanguage-differences
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.