Skip to content

Regression in 2.12.0: @Builder.Constructor with newBuilder + stagedBuilder generates uncompilable code #1630

@kidchen

Description

@kidchen

Regression in 2.12.0: @Builder.Constructor with newBuilder + stagedBuilder generates uncompilable code

Summary

Immutables 2.12.0 introduces a regression where using @Builder.Constructor with a style configuration containing both newBuilder = "builder" and stagedBuilder = true generates separate *BuilderStages.java files that cannot access the private constructors of the generated builder classes, resulting in compilation failures.

Environment

  • Immutables Version: 2.12.0 (regression from 2.11.0)
  • Java Version: 11, 17
  • Build Tool: Gradle, Maven

Issue Description

When using @Builder.Constructor on a class with a style that combines newBuilder = "builder" and stagedBuilder = true, Immutables 2.12.0 generates separate *BuilderStages.java files that attempt to instantiate builder classes with private constructors, causing compilation failures.

Minimal Reproduction Case

Style Configuration

@Target({ElementType.PACKAGE, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
@Value.Style(
    builderVisibility = BuilderVisibility.PUBLIC,
    newBuilder = "builder",           // ← Key factor
    stagedBuilder = true,             // ← Key factor
    strictBuilder = true,
    defaults = @Value.Immutable(copy = false),
    depluralize = true,
    typeAbstract = "_*",
    typeImmutable = "*",
    visibility = ImplementationVisibility.PUBLIC
)
public @interface MyStyle {
}

Source Class

@MyStyle
public class FooBarKey {
    
    protected final List<String> fields;
    
    @Builder.Constructor
    protected FooBarKey(String service,
                        String dataType, 
                        String keyVersion,
                        List<String> identifiers) {
        this.fields = List.of(service, dataType, keyVersion);
    }
    
    public List<String> getFields() {
        return fields;
    }
}

Expected Behavior (2.11.0)

  • Generates single file: FooBarKeyBuilder.java
  • Everything embedded in one class
  • Private constructors accessible within the same class
  • Compiles successfully

Actual Behavior (2.12.0)

  • Generates separate files:
    • FooBarKeyBuilder.java (contains private FooBarKeyBuilder() constructor)
    • FooBarKeyBuilderStages.java (contains return new FooBarKeyBuilder();)
  • Compilation fails with:
    error: FooBarKeyBuilder() has private access in FooBarKeyBuilder
    return new FooBarKeyBuilder();
           ^
    

Generated Code Analysis

FooBarKeyBuilderStages.java (Problematic)

public final class FooBarKeyBuilderStages {
  private FooBarKeyBuilderStages() {}

  public static BuildStart start() {
    return new FooBarKeyBuilder();  // ← Cannot access private constructor
  }
  // ...
}

FooBarKeyBuilder.java (Private Constructor)

public final class FooBarKeyBuilder {
  // ...
  private FooBarKeyBuilder() {  // ← Private constructor
  }
  // ...
}

Root Cause

The issue occurs when:

  1. @Builder.Constructor is used with newBuilder + stagedBuilder style
  2. Immutables 2.12.0 generates separate *BuilderStages.java files (new behavior)
  3. These separate files cannot access the private constructors of builder classes
  4. In 2.11.0, everything was generated in one file where private constructors were accessible

Affected Patterns

This regression affects both:

  • @Builder.Constructor with the problematic style ❌
  • @Builder.Factory with the problematic style ❌

Both patterns fail with the same root cause.

Workarounds

✅ Workaround 1: Remove newBuilder

@Value.Style(
    // newBuilder = "builder",  // ← Remove this line
    stagedBuilder = true,       // ← Keep staged builders
    // ... other settings
)

✅ Workaround 2: Remove stagedBuilder

@Value.Style(
    newBuilder = "builder",     // ← Keep custom naming
    // stagedBuilder = true,    // ← Remove staged builders
    // ... other settings
)

Test Case

The issue can be reproduced by:

  1. Creating the style and class shown above
  2. Compiling with Immutables 2.12.0
  3. Observing the compilation failure in the generated *BuilderStages.java file

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions