Local classes in Java
Classes declared inside a method.
A class that lives in a method
A local class is declared inside a method body. Like a local variable, it's visible only inside that block — other methods can't use it — and it takes no access modifier like public or private.
void report(List<Integer> xs) {
class Stats {
int max() { return Collections.max(xs); }
}
System.out.println(new Stats().max());
}Capturing locals
A local class can read the method's local variables and parameters — but only if they're final or effectively final, meaning never reassigned after they get their value. The same rule applies to lambdas.
Borrowing a local
What does this print?
void main() {
String sign = "!";
class Shout {
String up(String s) {
return s.toUpperCase() + sign;
}
}
System.out.println(new Shout().up("hey"));
}HEY!hey!Compile error: can't access sign
Show the answer
sign is never reassigned, so it's effectively final and the local class may capture it.
Modifying a captured local
count++ changes a captured local — and that's forbidden. Local classes (and lambdas) may only use effectively final locals, so this is a compile error. Neither a lambda nor a local class can modify captured locals.
int count = 0;
class Clicker {
void click() { count++; } // error
}Local records
Since Java 16 you can declare a record inside a method. What happens?
void main() {
int bonus = 5;
record Score(int pts) {
int total() { return pts + bonus; }
}
System.out.println(new Score(1).total());
}61Compile error
Show the answer
Local records, enums and interfaces (Java 16+) are implicitly static, so they can't capture the method's locals at all — not even effectively final ones.
Local class or lambda?
When is a local class a better fit than a lambda?
Think about it, then reveal the answer
When you need several methods or your own fields. A lambda implements exactly one method and can't declare fields. (Neither one can modify captured locals.)
Tiny helpers, tight scope
Developers often declare a local record as a temporary tuple inside a method — say, record NameScore(String name, int score) while processing results — so the helper type doesn't leak into the rest of the codebase.
Key takeaways
- Visible only inside the enclosing block
- Captures effectively final local variables
- No access modifiers like public or private
- Local records/enums/interfaces (Java 16+) can't use the method's locals
Local classes compile to files like Main$1Shout.class. The number lets two methods each declare their own Shout without the files clashing.
Practice questions
What does this print?
void main() {
String greeting = "Hi";
class Greeter {
String to(String n) { return greeting + " " + n; }
}
var g = new Greeter();
System.out.println(g.to("Sam"));
}- Hi Sam
- null Sam
- Compile error: can't access greeting
- Hi
Check your answer
Hi Sam. greeting is never reassigned, so it's effectively final and the local class may capture it.
What does this print?
void main() {
int count = 0;
class Clicker {
void click() { count++; }
}
new Clicker().click();
System.out.println(count);
}- 1
- 0
- Compile error
Check your answer
Compile error. count++ modifies a captured local. Local classes (like lambdas) may only use effectively final locals.