Skip to content

Unexpected behavior with conflicting generic parameter names in multiple interfaces using @RequestBody #45729

@Caryuter

Description

@Caryuter

Summary

When implementing multiple interfaces that define default methods with generic type parameters using the same name (e.g., C), Spring's mapping logic fails to correctly resolve the type used in a @RequestBody annotated method. Changing the name of the type parameter in one interface to a different letter (e.g., from C to A) solves the issue.

This seems to be related to how Spring resolves generic type information in the presence of inheritance with conflicting type parameter names.

Example Setup

public interface BatchCreateController<E, DTO, C, D> {
    @PostMapping("/batch")
    default ResponseEntity<DTO> batchCreate(@RequestBody List<C> dto) {
        // ...
    }
}

public abstract class CurrentUserUnmodifiableController<DTO, E, C, D, S>
    implements CurrentUserCreateController<E, DTO, C, D> {
    // ...
}

public class CartController extends CurrentUserUnmodifiableController<CartResponse, Cart, EmptyCreateCart, Detail, Long>
    implements BatchCreateController<Cart, CartResponse, AddCartEntryRequest, Detail> {

    // ...
}

In this setup, both interfaces use C as a generic name but refer to different concrete types (EmptyCreateCart vs AddCartEntryRequest).
Here are the DTOs:

@Data
@NoArgsConstructor
@Builder
public class EmptyCreateCart implements Serializable{

}


@With
@Builder
public record AddCartEntryRequest(
        @NotBlank(message = "{product.barcode.notblank}") @Size(min = 12, max = 13, message = "{product.barcode.size}") @Pattern(regexp = "^\\d{12,13}$", message = "{product.barcode.pattern}") String barcode,
        @NotNull(message = "{ticket.quantity.notnull}") @Positive(message = "{ticket.quantity.positive}") Integer quantity) implements Serializable, SubKeyIdentifiable<String> {

    @JsonIgnore
    @Override
    public String getSubId() {
        return barcode();
    }

}

Problem

When starting the application, the endpoint mapped at /v1/me/carts/batch was recognized, but the C parameter type in List<C> was of type passed to CurrentUserUnmodifiableController and not BatchCreateController

Workaround / Solution

Simply renaming the generic type parameter from C to another name like A in BatchCreateController resolved the issue.

public interface BatchCreateController<E, DTO, A, D> {
    @PostMapping("/batch")
    default ResponseEntity<DTO> batchCreate(@RequestBody List<A> dto) {
        // Now it works
    }
}

Expected Behavior

Spring should either:

  • Handle this case safely even with identical generic parameter names across interfaces.

  • Or provide an error/warning message about ambiguous generic type resolution.

Environment

  • Spring Boot: 3.4.4

  • Java: 21

  • No custom deserializers or controllers involved beyond standard @RestController

Metadata

Metadata

Assignees

No one assigned

    Labels

    for: external-projectFor an external project and not something we can fix

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions