Double Brace Initialization_

The (double) {{cool}} initialization in Java...

If you've been in the industry long enough, you might have encountered some "cool" engineers who use a little trick called Double-Brace initialization in their Java code – or you might be one of them.

Double-Brace What?

In case you don't know, Double-Brace initialization is a technique that initializes objects inline by doing some *black magek* to make our Java code look cool.

The most popular use case for Double-Brace initialization is with collections:

final var array = new ArrayList<String>(){{
	add("Anas");
	add("cat");
	add("Rust");
}};
			

But it isn't limited to collections, you can use Double-Brace initialization _virtually_ with any class

public class CustomDoubleBraceInitialization {
	private static class Thing {
		String name;
		int id;

		void setName(final String n) { this.name = n; }

		@Override
		public String toString() { return "The thing name: " + name + ", ID: " + id; }
	}

	public static void main(String[] args) {
			final var thing = new Thing() {{
			setName("Anas");
			id = 1;
			}};
			System.out.println(thing);
		}
}
			

Cool, right?

But what's the catch?

As you know, as a software engineers, we can't just live like that and like: Okay cool trick and continue our life, so... Let's dive deeper

First, let's examine how the normal (boring) way works:

import java.util.ArrayList;

public class NormalInitialization {
    public static void main(String[] args) {
   	    final var array = new ArrayList<String>();
        array.add("Anas");
       	array.add("cat");
       	array.add("Rust");

       	System.out.println(array);
    }
}
			

Yew, boring; but let's not let the outside fool us, we all know that the real beauty is from the inside right? Right?

when we compile this, javac will give us _one_ .class file, and when we use javap on that file we get something like:

...
{
public NormalInitialization();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
    stack=1, locals=1, args_size=1
        0: aload_0
        1: invokespecial #1                  // Method java/util/ArrayList."<init>":()V
        4: return
    LineNumberTable:
        line 3: 0

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
    stack=2, locals=2, args_size=1
        0: new           #7                  // class java/util/ArrayList
        3: dup
        4: invokespecial #9                  // Method java/util/ArrayList."<init>":()V
        7: astore_1
        8: aload_1
        9: ldc           #10                 // String Anas
        11: invokevirtual #12                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        14: pop
        15: aload_1
        16: ldc           #16                 // String cat
        18: invokevirtual #12                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        21: pop
        22: aload_1
        23: ldc           #18                 // String Rust
        25: invokevirtual #12                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        28: pop
        29: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        32: aload_1
        33: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        36: return
    LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 15
        line 8: 22
        line 10: 29
        line 11: 36
}
SourceFile: "NormalInitialization.java"
			

You might be wondering, "WTF is this?" Well, this is our glorious bytecode in its textual representation – the actual code that gets executed in the JVM. Let's examine it more closely.

First, let's focus on the relevant part – the code inside our main method:

...
    Code:
    stack=2, locals=2, args_size=1
        0: new           #7                  // class java/util/ArrayList
        3: dup
        4: invokespecial #9                  // Method java/util/ArrayList."<init>":()V
        7: astore_1
        8: aload_1
        9: ldc           #10                 // String Anas
        11: invokevirtual #12                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        14: pop
        15: aload_1
        16: ldc           #16                 // String cat
        18: invokevirtual #12                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        21: pop
        22: aload_1
        23: ldc           #18                 // String Rust
        25: invokevirtual #12                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        28: pop
    ...
			

Much cleaner. Now let's examine these instructions one by one.

Before getting our hands dirty, an important note: The JVM is a simple stack-based machine. Instead of registers, we have the stack where we perform push and pop operations and the locals array to store our variables.

Let's break down the bytecode instructions:

  1. 0: new #7 - Creates new _uninitialized_ ArrayList object on top of the stack (push)
  2. 3: dup - Duplicates the top stack value, which its our ArrayList object that we've just created (push)
  3. 4: invokespecial #9 - Calls the constructor (a.k.a. init function), thats consumes the top value on the stack to construct our object (pop)
  4. 7: astore_1 - Stores the remaining reference in local variable slot 1 (pop)
  5. 8: aload_1 - Loads the ArrayList reference (from local variable 1) onto the stack. (push)
  6. 9: ldc #10 - Loads the String constant "Anas" (from constant pool entry #10) onto the stack (push)
  7. 11: invokevirtual #12 - Calls ArrayList.add(Object) (constant pool entry #12), it consumes the first two items on the stack(this, and Oblect) and it pushes one (the returned boolean) (pop, pop, push)
  8. 14: pop - discards the boolean result (since we don’t use it).
  9. And just like that the rest instruction do the same operation to add the rest items

Pretty simple bytecode. Now, let's peel back the layers of our 'clever' double-brace initialization and see if it's truly as elegant as it appears.

import java.util.ArrayList;

public class DoubleBraceInitialization {
   	public static void main(String[] args) {
  		final var array = new ArrayList<String>() {
 			{
    				add("Anas");
    				add("cat");
    				add("Rust");
 			}
  		};

  		System.out.println(array);
   	}
}
			

Awww, so aduorable :3, let's see its bytecode

Classfile /tmp/java-lab/DoubleBraceInitialization.class
  Last modified Apr 10, 2025; size 542 bytes
  SHA-256 checksum c6e4084b962996a9c42ffdccc26e129f429ebcb21020e7f1ff70e86c3d018faf
  Compiled from "DoubleBraceInitialization.java"
public class DoubleBraceInitialization
  minor version: 0
  major version: 68
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #22                         // DoubleBraceInitialization
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 3
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // DoubleBraceInitialization$1
   #8 = Utf8               DoubleBraceInitialization$1
   #9 = Methodref          #7.#3          // DoubleBraceInitialization$1."<init>":()V
  #10 = Fieldref           #11.#12        // java/lang/System.out:Ljava/io/PrintStream;
  #11 = Class              #13            // java/lang/System
  #12 = NameAndType        #14:#15        // out:Ljava/io/PrintStream;
  #13 = Utf8               java/lang/System
  #14 = Utf8               out
  #15 = Utf8               Ljava/io/PrintStream;
  #16 = Methodref          #17.#18        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #17 = Class              #19            // java/io/PrintStream
  #18 = NameAndType        #20:#21        // println:(Ljava/lang/Object;)V
  #19 = Utf8               java/io/PrintStream
  #20 = Utf8               println
  #21 = Utf8               (Ljava/lang/Object;)V
  #22 = Class              #23            // DoubleBraceInitialization
  #23 = Utf8               DoubleBraceInitialization
  #24 = Utf8               Code
  #25 = Utf8               LineNumberTable
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               SourceFile
  #29 = Utf8               DoubleBraceInitialization.java
  #30 = Utf8               NestMembers
  #31 = Utf8               InnerClasses
{
  public DoubleBraceInitialization();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class DoubleBraceInitialization$1
         3: dup
         4: invokespecial #9                  // Method DoubleBraceInitialization$1."<init>":()V
         7: astore_1
         8: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        11: aload_1
        12: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        15: return
      LineNumberTable:
        line 5: 0
        line 13: 8
        line 14: 15
}
SourceFile: "DoubleBraceInitialization.java"
NestMembers:
  DoubleBraceInitialization$1
InnerClasses:
  #7;                                     // class DoubleBraceInitialization$1

			 

The reason that i bought the full output of javap command this time that because their's some magek happening here

The constant pool is a fundamental part of the Java Virtual Machine (JVM) architecture. It's a runtime data structure that stores various constants and symbolic references needed for class execution.

So, let's analyze this bytecode more closely. We'll focus on three key sections: the Constant pool, the Code segment, and the sneaky InnerClasses declaration. First, we'll examine the Cnstant pool section.

...
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // DoubleBraceInitialization$1
   #8 = Utf8               DoubleBraceInitialization$1
   #9 = Methodref          #7.#3          // DoubleBraceInitialization$1."<init>":()V
  #10 = Fieldref           #11.#12        // java/lang/System.out:Ljava/io/PrintStream;
  #11 = Class              #13            // java/lang/System
  #12 = NameAndType        #14:#15        // out:Ljava/io/PrintStream;
  #13 = Utf8               java/lang/System
  #14 = Utf8               out
  #15 = Utf8               Ljava/io/PrintStream;
  #16 = Methodref          #17.#18        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #17 = Class              #19            // java/io/PrintStream
  #18 = NameAndType        #20:#21        // println:(Ljava/lang/Object;)V
  #19 = Utf8               java/io/PrintStream
  #20 = Utf8               println
  #21 = Utf8               (Ljava/lang/Object;)V
  #22 = Class              #23            // DoubleBraceInitialization
  #23 = Utf8               DoubleBraceInitialization
  #24 = Utf8               Code
  #25 = Utf8               LineNumberTable
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               SourceFile
  #29 = Utf8               DoubleBraceInitialization.java
  #30 = Utf8               NestMembers
  #31 = Utf8               InnerClasses
  ....

			 

Wait a sec, where is our strings? isn't those supposed to be constants?

Actually, yes, any hard coded value should end in the constant pool, but where is our strings then?

and if we lock closer we'll spot a strange line in our pool, actually *lines*

...
   #7 = Class              #8             // DoubleBraceInitialization$1
   #8 = Utf8               DoubleBraceInitialization$1
  ...
  #30 = Utf8               NestMembers
  #31 = Utf8               InnerClasses
  ....

			 

I don't remember having an inner class in our code, where that comes from?

Hmm.. let's look at the code section, maybe it explains something

...
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class DoubleBraceInitialization$1
         3: dup
         4: invokespecial #9                  // Method DoubleBraceInitialization$1."<init>":()V
         7: astore_1
         8: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        11: aload_1
        12: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        15: return
...
			 

Notice something interesting here? Unlike our previous example there's no explicit add() method calls or any mention to our strings

Instead we're creating an instance from a mysterious DoubleBraceInitialization$1 class:

...
         0: new           #7                  // class DoubleBraceInitialization$1
         3: dup
         4: invokespecial #9                  // Method DoubleBraceInitialization$1."<init>":()V
         7: astore_1
...
			 

This is the anonymous subclass that double-brace initialization creates behind the scenes.

Looking at the InnerClasses section confirms this:

 InnerClasses: #7; // class DoubleBraceInitialization$1
                

This shows that our double-brace initialization actually creates a new anonymous inner class that extends ArrayList. The initialization block (the second set of braces) becomes the instance initializer for this anonymous class.

If we examine the generated DoubleBraceInitialization$1.class file, we’ll see the true cost of double-brace initialization:

Classfile /tmp/java-lab/DoubleBraceInitialization$1.class
    Last modified Apr 10, 2025; size 545 bytes
    SHA-256 checksum b2aea284a68cf6aa5d97dcbc1c5bb12915db8cb6e8f1bbfe3fc152873aa7cc53
    Compiled from "DoubleBraceInitialization.java"
class DoubleBraceInitialization$1 extends java.util.ArrayList<java.lang.String>
    minor version: 0
    major version: 68
    flags: (0x0020) ACC_SUPER
    this_class: #10                         // DoubleBraceInitialization$1
    super_class: #2                         // java/util/ArrayList
    interfaces: 0, fields: 0, methods: 1, attributes: 5
Constant pool:
    #1 = Methodref          #2.#3          // java/util/ArrayList."<init>":()V
    #2 = Class              #4             // java/util/ArrayList
    #3 = NameAndType        #5:#6          // "<init>":()V
    #4 = Utf8               java/util/ArrayList
    #5 = Utf8               <init>
    #6 = Utf8               ()V
    #7 = String             #8             // Anas
    #8 = Utf8               Anas
    #9 = Methodref          #10.#11        // DoubleBraceInitialization$1.add:(Ljava/lang/Object;)Z
    #10 = Class              #12            // DoubleBraceInitialization$1
    #11 = NameAndType        #13:#14        // add:(Ljava/lang/Object;)Z
    #12 = Utf8               DoubleBraceInitialization$1
    #13 = Utf8               add
    #14 = Utf8               (Ljava/lang/Object;)Z
    #15 = String             #16            // cat
    #16 = Utf8               cat
    #17 = String             #18            // Rust
    #18 = Utf8               Rust
    #19 = Utf8               Code
    #20 = Utf8               LineNumberTable
    #21 = Utf8               Signature
    #22 = Utf8               Ljava/util/ArrayList<Ljava/lang/String;>;
    #23 = Utf8               SourceFile
    #24 = Utf8               DoubleBraceInitialization.java
    #25 = Utf8               EnclosingMethod
    #26 = Class              #27            // DoubleBraceInitialization
    #27 = Utf8               DoubleBraceInitialization
    #28 = NameAndType        #29:#30        // main:([Ljava/lang/String;)V
    #29 = Utf8               main
    #30 = Utf8               ([Ljava/lang/String;)V
    #31 = Utf8               NestHost
    #32 = Utf8               InnerClasses
{
    DoubleBraceInitialization$1();
    descriptor: ()V
    flags: (0x0000)
    Code:
        stack=2, locals=1, args_size=1
            0: aload_0
            1: invokespecial #1                  // Method java/util/ArrayList."<init>":()V
            4: aload_0
            5: ldc           #7                  // String Anas
            7: invokevirtual #9                  // Method add:(Ljava/lang/Object;)Z
        10: pop
        11: aload_0
        12: ldc           #15                 // String cat
        14: invokevirtual #9                  // Method add:(Ljava/lang/Object;)Z
        17: pop
        18: aload_0
        19: ldc           #17                 // String Rust
        21: invokevirtual #9                  // Method add:(Ljava/lang/Object;)Z
        24: pop
        25: return
        LineNumberTable:
        line 5: 0
        line 7: 4
        line 8: 11
        line 9: 18
        line 1: 25
}
Signature: #22                          // Ljava/util/ArrayList<Ljava/lang/String;>;
SourceFile: "DoubleBraceInitialization.java"
EnclosingMethod: #26.#28                // DoubleBraceInitialization.main
NestHost: class DoubleBraceInitialization
InnerClasses:
    #10;                                    // class DoubleBraceInitialization$1
                

Noticed the class definition?

class DoubleBraceInitialization$1 extends java.util.ArrayList<java.lang.String>
                

it secretly creates an anonymous inner class that extends ArrayList. The second set of braces—the initialization block—becomes an instance initializer in this anonymous class, containing all our add() calls.

This is why the main class’s bytecode appears simpler: the initialization logic is offloaded to the hidden class.

Isn't that a good thing?

No.

By using double-brace initializtion you'r adding complixty, increasing memory usage, leaking memory, making serialization/deserialization harder, killing kittens, and frocing Java to be someone she's clearly isn't.

Each time you use double brace initialisation a new class is made

Map source = new HashMap(){{
    put("firstName", "John");
    put("lastName", "Smith");
    put("organizations", new HashMap(){{
        put("0", new HashMap(){{
            put("id", "1234");
        }});
        put("abc", new HashMap(){{
            put("id", "5678");
        }});
    }});
}};
                

... will produce these classes:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
                

Just immagen being the poor JVM, who has to load all these classes and execute them and keeping track of them.

The complete story

So, with some compiler hacking we can see what's the final code after the annotation stage or desuggring if we can say

public class DoubleBraceInitialization {

    public DoubleBraceInitialization() {
        super();
    }

    public static void main(String[] args) {
        final DoubleBraceInitialization$1 array = new ArrayList<String>(){

            () {
                super();
            }
            {
                add("Anas");
                add("cat");
                add("Rust");
            }
        };
        System.out.println(array);
    }
}
                

and that's an prove that what matters most is the inside, not the outside

"Every time someone uses double brace initialisation, a kitten gets killed." – Anonymous Stack Overflow comment

Bye..