Java
Inner Classes
Anonymous Classes
Memory Leaks
Software Development

When exactly is it leak safe to use anonymous inner classes?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Anonymous inner classes in Java hold an implicit reference to their enclosing instance. This reference prevents the enclosing object from being garbage collected as long as the anonymous class instance is alive. Memory leaks occur when the anonymous class outlives its enclosing object — typically in Android Activities, long-lived listeners, or cached callbacks. It is safe to use anonymous inner classes when their lifetime is shorter than or equal to the enclosing object's lifetime, or when the enclosing class is a static context with no instance reference.

How the Implicit Reference Works

java
1public class Outer {
2    private String data = "important data";
3
4    public Runnable createTask() {
5        // This anonymous class holds a reference to 'this' (the Outer instance)
6        return new Runnable() {
7            @Override
8            public void run() {
9                System.out.println(data);  // Accesses Outer.this.data
10            }
11        };
12    }
13}

The compiler generates a hidden field in the anonymous class that stores a reference to the Outer instance. Even if you do not reference any field from Outer, the reference still exists:

java
1// Even this holds a reference to Outer:
2return new Runnable() {
3    @Override
4    public void run() {
5        System.out.println("Hello");  // No access to Outer fields
6    }
7};
8// The Outer reference is still captured by the compiler

When It Is Safe (No Leak Risk)

1. Anonymous Class Lives Shorter Than Enclosing Object

java
1public class DataProcessor {
2    private List<String> items;
3
4    public List<String> filterItems() {
5        // Safe: the Predicate dies when filterItems() returns
6        return items.stream()
7            .filter(new Predicate<String>() {
8                @Override
9                public boolean test(String s) {
10                    return s.length() > 3;
11                }
12            })
13            .collect(Collectors.toList());
14    }
15}

The anonymous Predicate is created and garbage collected within the method call. It never outlives the DataProcessor.

2. Enclosing Object Is Long-Lived (Application/Singleton Scope)

java
1public class Application {
2    // Safe: Application lives for the entire program
3    private final Comparator<String> comparator = new Comparator<String>() {
4        @Override
5        public int compare(String a, String b) {
6            return a.compareToIgnoreCase(b);
7        }
8    };
9}

If the enclosing object is a singleton or lives for the program's duration, holding a reference to it does not cause a leak.

3. Static Context (No Enclosing Instance)

java
1public class Utils {
2    // Safe: defined in a static method — no enclosing instance
3    public static Runnable createTask() {
4        return new Runnable() {
5            @Override
6            public void run() {
7                System.out.println("No implicit reference to any instance");
8            }
9        };
10    }
11}

Anonymous classes defined in static methods or static initializers do not hold a reference to any instance.

When It Causes Leaks

1. Android Activity with Handler/Runnable

java
1// LEAK: Activity cannot be garbage collected
2public class MyActivity extends Activity {
3    @Override
4    protected void onCreate(Bundle savedInstanceState) {
5        super.onCreate(savedInstanceState);
6
7        new Handler().postDelayed(new Runnable() {
8            @Override
9            public void run() {
10                // This anonymous Runnable holds a reference to MyActivity
11                updateUI();
12            }
13        }, 60000);  // 60 seconds — Activity may be destroyed before this runs
14    }
15}

If the user rotates the screen or navigates away, the Activity is destroyed but the Runnable on the Handler's message queue still holds a reference, preventing garbage collection.

2. Registering Listeners That Are Never Removed

java
1// LEAK: listener lives as long as the EventBus
2public class UserProfile {
3    public UserProfile(EventBus eventBus) {
4        eventBus.register(new EventListener() {
5            @Override
6            public void onEvent(Event e) {
7                // Holds reference to UserProfile
8                refreshProfile();
9            }
10        });
11        // If UserProfile is discarded but listener is never unregistered,
12        // UserProfile cannot be garbage collected
13    }
14}

3. Caching or Storing in a Collection

java
1// LEAK: the cache holds the anonymous class which holds the enclosing object
2public class ExpensiveObject {
3    public void registerCallback(Map<String, Callback> cache) {
4        cache.put("key", new Callback() {
5            @Override
6            public void onComplete() {
7                // Holds reference to ExpensiveObject
8            }
9        });
10        // ExpensiveObject cannot be GC'd as long as cache has this entry
11    }
12}

The Fix: Static Inner Classes

java
1public class MyActivity extends Activity {
2    // Static inner class — no implicit reference to Activity
3    private static class SafeRunnable implements Runnable {
4        private final WeakReference<MyActivity> activityRef;
5
6        SafeRunnable(MyActivity activity) {
7            this.activityRef = new WeakReference<>(activity);
8        }
9
10        @Override
11        public void run() {
12            MyActivity activity = activityRef.get();
13            if (activity != null) {
14                activity.updateUI();
15            }
16        }
17    }
18
19    @Override
20    protected void onCreate(Bundle savedInstanceState) {
21        super.onCreate(savedInstanceState);
22        new Handler().postDelayed(new SafeRunnable(this), 60000);
23    }
24}

Static inner classes do not capture the enclosing instance. Use WeakReference if you need access to the enclosing object but do not want to prevent its garbage collection.

The Fix: Lambdas (Java 8+)

java
1// Lambdas that don't capture 'this' are safe
2public static Comparator<String> caseInsensitive() {
3    return (a, b) -> a.compareToIgnoreCase(b);  // No enclosing instance captured
4}
5
6// But lambdas in instance methods still capture 'this' if they reference instance members
7public class MyClass {
8    private int threshold = 5;
9
10    public Predicate<Integer> getFilter() {
11        return n -> n > threshold;  // Captures 'this' to access threshold
12    }
13}

Lambdas follow the same capture rules as anonymous classes but the compiler can optimize non-capturing lambdas to avoid the reference.

Common Pitfalls

  • Assuming no field access means no reference: The Java compiler captures a reference to the enclosing instance in every non-static anonymous class, even if no fields or methods of the enclosing class are used. The reference exists regardless.
  • Long-delayed Handler callbacks in Android: Handler.postDelayed() with an anonymous Runnable is the most common source of Activity leaks. The Runnable lives on the message queue and holds the Activity reference until it executes or is removed.
  • Registering listeners without unregistering: Anonymous listener instances registered with event buses, broadcast receivers, or observable objects prevent the enclosing object from being garbage collected until the listener is explicitly removed.
  • Using anonymous classes in static collections: Adding an anonymous class instance to a static Map or List pins the enclosing object in memory for the lifetime of the application, since static fields are never garbage collected.
  • Confusing inner classes with static nested classes: class Inner {} inside another class is an inner class with an implicit outer reference. static class Nested {} has no such reference. When defining helper classes, always prefer static unless you specifically need access to the enclosing instance.

Summary

  • Anonymous inner classes always hold an implicit reference to their enclosing instance (unless in a static context)
  • Safe to use when the anonymous class's lifetime is shorter than the enclosing object
  • Safe in static methods, singletons, and application-scoped objects
  • Dangerous with delayed callbacks, long-lived listeners, and cached references
  • Use static inner classes with WeakReference to break the reference chain
  • In Java 8+, non-capturing lambdas avoid the implicit reference, but instance-method lambdas that access fields still capture this

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.