Skip to content

Eliminate 2,870 lines of boilerplate using @Mixin annotation processor#37089

Open
verhasi wants to merge 1 commit into
spring-projects:mainfrom
verhasi:add-mixin
Open

Eliminate 2,870 lines of boilerplate using @Mixin annotation processor#37089
verhasi wants to merge 1 commit into
spring-projects:mainfrom
verhasi:add-mixin

Conversation

@verhasi

@verhasi verhasi commented Jul 26, 2026

Copy link
Copy Markdown

Summary

This PR eliminates 2,870 lines of delegation boilerplate across 50 files in the Spring Framework using the Mixin annotation processor. All changes are compile-time only with zero runtime dependencies and all existing tests pass.

Motivation

Spring Framework contains significant boilerplate code in decorator and wrapper classes. Analysis identified 141 files with delegation patterns that require ongoing maintenance whenever interfaces change. This PR addresses the highest-impact cases.

Previous Discussion (PR #34977, May 2025)

Last year we proposed this approach for a single file (MultiValueMapAdapter). The Spring team's feedback was:

  • Concerns about build complexity
  • Not identified as a maintenance problem at that scale
  • Suggested to "reconsider later"

This PR demonstrates the approach at scale - proving the value and showing minimal complexity.

What Changed

Files Refactored (50 total across 10 modules)

spring-web (11 files)

  • ClientHttpResponseDecorator (web.client) - 5 methods
  • ClientHttpResponseDecorator (reactive) - 5 methods
  • ClientHttpRequestDecorator (reactive) - 12 methods
  • EscapedErrors - 17 methods
  • HttpRequestWrapper - 4 methods
  • HttpExchangeAdapterDecorator - 6 methods
  • MockWebSession (testFixtures) - 12 methods
  • And 4 more...

spring-webmvc (4 files)

  • DelegatingWebMvcConfiguration - 18 methods (complex case with special handling)
  • VersionResourceResolver.FileNameVersionedResource - 16 methods (static inner class)
  • And 2 more...

spring-websocket (2 files)

  • WebSocketSessionDecorator - 17 methods
  • WebSocketHandlerDecorator - 5 methods

Other modules:

  • spring-test, spring-beans, spring-core, spring-jms, spring-r2dbc, spring-webflux, spring-context

Impact

  • Lines eliminated: 2,870 lines of boilerplate
  • Methods auto-generated: ~750 forwarding methods
  • Runtime impact: Zero (compile-time only)
  • Test coverage: All existing tests pass

Technical Details

How Mixin Works

  1. Compile-time code generation - No runtime dependency
  2. Generated classes extend parent and implement interfaces
  3. Annotation processor generates forwarding methods automatically
  4. Type-safe - Compiler validates all generated code

Example

Before:

public class WebSocketSessionDecorator implements WebSocketSession {
    private final WebSocketSession delegate;
    
    public WebSocketSessionDecorator(WebSocketSession delegate) {
        this.delegate = delegate;
    }
    
    @Override public String getId() { return delegate.getId(); }
    @Override public Map<String, Object> getAttributes() { return delegate.getAttributes(); }
    @Override public void start() { delegate.start(); }
    // ... 14 more identical forwarding methods
}

After:

public class WebSocketSessionDecorator extends WebSocketSessionDecoratorForwarder 
        implements WebSocketSession {
    
    @Mixin
    public WebSocketSessionDecorator(WebSocketSession delegate) {
        super(delegate);
        Assert.notNull(delegate, "Delegate must not be null");
    }
    
    // Only custom logic remains - forwarding is auto-generated
}

Build Changes

  1. New module: framework-annotation-processor

    • Claims unclaimed annotations to prevent -Werror failures
    • Required for any annotation processing in Spring's build
  2. Dependencies added (per module):

    compileOnly("guru.mocker.annotation:mixin-annotation:1.1.18")
    annotationProcessor("guru.mocker.annotation:mixin-annotation-processor:1.1.18")
    annotationProcessor(project(":framework-annotation-processor"))
  3. No runtime dependencies - compileOnly means zero runtime impact

Benefits

1. Reduced Maintenance Burden

  • Interface changes automatically propagate to all decorators
  • No need to manually update 750+ forwarding methods
  • Compiler ensures type safety

2. Improved Code Clarity

  • Only business logic visible in decorator classes
  • Boilerplate hidden in generated code
  • Easier to review and understand

3. Consistency

  • All decorators follow same pattern
  • Generated code is uniform and correct
  • No copy-paste errors

4. Zero Runtime Cost

  • Compile-time only
  • No reflection
  • Same bytecode as hand-written delegation
  • No performance impact

Testing

  • ✅ All module tests pass
  • ✅ Full Spring Framework build succeeds
  • ✅ No runtime behavior changes
  • ✅ Generated code is functionally identical to original

Addressing Previous Concerns

"Build Complexity"

Proven minimal: 50 files refactored, all builds succeed. Standard annotation processing that IDEs handle well.

"Not a Maintenance Problem"

Scale matters: 141 files identified, 2,870 lines of boilerplate. Every interface change requires updating all decorators manually.

"New Dependency"

Compile-time only: Zero runtime dependency. Only adds annotation processor for code generation.

Future Work

This PR addresses 50 of the 141 identified files. Additional candidates remain that could benefit from the same approach, but we chose to demonstrate the pattern first with high-impact, straightforward cases.

Breaking Changes

None. This is purely a refactoring - all public APIs remain identical.


One year ago we proposed this for one file. Today we demonstrate it works at scale across Spring Framework with 2,870 lines eliminated and all tests passing.

We believe this strikes the right balance between reducing maintenance burden and keeping the build straightforward.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 26, 2026
Apply the Mixin annotation processor to eliminate delegation boilerplate
across 50 files in Spring Framework. All changes are compile-time only with
zero runtime dependencies.

Refactored files (50 total):
- spring-web: 11 files including decorators, wrappers, and test utilities
- spring-webmvc: 4 files including DelegatingWebMvcConfiguration
- spring-websocket: 2 decorators
- spring-test: MockWebSession
- spring-beans, spring-core, spring-jms, spring-r2dbc, spring-webflux
- Plus inner classes and test fixtures

Changes:
- Add framework-annotation-processor module to claim unclaimed annotations
- Add Mixin dependencies to affected modules (compile-time only)
- Refactor 50 decorator/wrapper classes to use @mixin
- Remove 2,870 lines of forwarding boilerplate
- Auto-generate ~750 forwarding methods

Technical details:
- Zero runtime dependency (compileOnly scope)
- All existing tests pass
- Generated code is functionally identical to original
- No breaking changes to public APIs

Benefits:
- Reduced maintenance burden for interface changes
- Improved code clarity (only business logic visible)
- Consistent pattern across all decorators
- Zero performance impact

This builds on the approach proposed in PR spring-projects#34977 (May 2025), now
demonstrated at scale with proven results.

Signed-off-by: Verhás István <istvan@verhas.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants