Skip to content

DynamoDB enhanced client with Immutables - can't use @Value.Default and @DynamoDbIgnore on the same method #5950

Open

Description

@Sinbios

Describe the bug

Hi, I'm struggling with something that seems like it should be simple using the DDB enhanced client with the Immutables library. I'm trying to annotate a method with both @Value.Default and @DynamoDbIgnore, but I keep getting java.lang.IllegalArgumentException: A method was found on the immutable class builder that does not appear to have a matching getter on the immutable class. Use the @DynamoDbIgnore annotation on the method if you do not want it to be included in the TableSchema introspection. which seems strange since the method is already so annotated.

It seems like the issue is that the @DynamoDbIgnore causes the method to be filtered out on the immutable class in ImmutableIntrospector, but the annotation isn't present on the builder class method so it's not filtered out for the builder class methods, resulting in a mismatch.

Is this use case simply not supported? Using Immutables 2.10.1.

Regression Issue

  • Select this option if this issue appears to be a regression.

Expected Behavior

Successfully generate an Immutable DynamoDB table with a value that defaults to the return value if not set in the builder, and is ignored for DynamoDB persistence.

Current Behavior

Got the following exception when instantiating the table:

    org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable<com.abc.ddb.models.Customer> arg1] in constructor [com.abc.ddb.managers.impl.DynamoDbHelperTest(software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable<com.abc.ddb.models.CustomerReportingAutoRemoval>,software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable<com.abc.ddb.models.Customer>)]: A method was found on the immutable class builder that does not appear to have a matching getter on the immutable class. Use the @DynamoDbIgnore annotation on the method if you do not want it to be included in the TableSchema introspection. [Method = "public final com.abc.ddb.models.CustomerBuilder com.abc.ddb.models.CustomerBuilder.name(java.lang.String)"]
        at app//org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameter(ParameterResolutionUtils.java:159)
        at app//org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters(ParameterResolutionUtils.java:103)
        at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:59)
        at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:363)
        at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:310)
        at app//org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
        at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:286)
        at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:278)
        at java.base@17.0.14/java.util.Optional.orElseGet(Optional.java:364)
        at app//org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:277)
        at app//org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:105)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:104)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:68)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90)
        at java.base@17.0.14/java.util.ArrayList.forEach(ArrayList.java:1511)
        at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
        at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
        at java.base@17.0.14/java.util.ArrayList.forEach(ArrayList.java:1511)
        at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
        at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
        at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
        at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
        at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
        at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
        at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
        at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:124)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:99)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:94)
        at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
        at java.base@17.0.14/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base@17.0.14/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base@17.0.14/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base@17.0.14/java.lang.reflect.Method.invoke(Method.java:569)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
        at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92)
        at jdk.proxy2/jdk.proxy2.$Proxy6.stop(Unknown Source)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:200)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
        at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
        at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121)
        at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
        at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
        at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

        Caused by:
        java.lang.IllegalArgumentException: A method was found on the immutable class builder that does not appear to have a matching getter on the immutable class. Use the @DynamoDbIgnore annotation on the method if you do not want it to be included in the TableSchema introspection. [Method = "public final com.abc.ddb.models.CustomerBuilder com.abc.ddb.models.CustomerBuilder.name(java.lang.String)"]
            at software.amazon.awssdk.enhanced.dynamodb.internal.immutable.ImmutableIntrospector.generateExceptionForMethod(ImmutableIntrospector.java:134)
            at software.amazon.awssdk.enhanced.dynamodb.internal.immutable.ImmutableIntrospector.introspect(ImmutableIntrospector.java:96)
            at software.amazon.awssdk.enhanced.dynamodb.internal.immutable.ImmutableIntrospector.getImmutableInfo(ImmutableIntrospector.java:65)
            at software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema.createStaticImmutableTableSchema(ImmutableTableSchema.java:204)
            at software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema.create(ImmutableTableSchema.java:172)
            at software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema.lambda$create$0(ImmutableTableSchema.java:140)
            at java.base/java.util.Map.computeIfAbsent(Map.java:1054)
            at java.base/java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2760)
            at software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema.create(ImmutableTableSchema.java:139)
            at software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema.create(ImmutableTableSchema.java:161)
            at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromImmutableClass(TableSchema.java:164)
            at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromClass(TableSchema.java:204)
            at com.amazon.ddblocal.DefaultTableSchemaProvider.getTableSchema(DefaultTableSchemaProvider.java:13)
            at com.amazon.ddblocal.DynamoDbEnhancedLocalExtension.resolveParameter(DynamoDbEnhancedLocalExtension.java:129)
            at org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameter(ParameterResolutionUtils.java:136)
            ... 72 more

Reproduction Steps

import org.immutables.value.Value;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

@Value.Immutable
@Value.Style(
  builtinContainerAttributes = false,
  defaults = @Value.Immutable(copy = false),
  depluralize = true,
  get = {"get*", "has*", "is*"},
  implementationNestedInBuilder = true,
  overshadowImplementation = true,
  strictBuilder = true,
  typeBuilder = "*Builder",
  passAnnotations = {
    // Copy all Enhanced Client annotations to the immutable class
    DynamoDbAttribute.class,
    DynamoDbIgnore.class,
    DynamoDbPartitionKey.class,
    DynamoDbSecondaryPartitionKey.class,
    DynamoDbSecondarySortKey.class,
    DynamoDbSortKey.class
  })
@DynamoDbImmutable(builder = CustomerBuilder.class)
public interface Customer {

  @DynamoDbPartitionKey
  String getCustomerId();

  @Value.Default
  @DynamoDbIgnore
  default String getName() {
    return "";
  }
}

Possible Solution

No response

Additional Information/Context

No response

AWS Java SDK version used

2.30.32

JDK version used

openjdk 17.0.14 2025-01-21 LTS

Operating System and version

Amazon Linux 2

Activity

  1. added
    bugThis issue is a bug.
    needs-triageThis issue or PR still needs to be triaged.
    on Mar 12, 2025
  2. self-assigned this
    on Aug 28, 2026
  3. removed their assignment
    on Sep 23, 2026
  4. self-assigned this
    on Oct 28, 2026
  5. adev-code commented on Oct 28, 2026

    @adev-code
    Contributor

    Hi @Sinbios, thanks for reaching out. I reproduced this with your class and Immutables 2.10.1 on SDK 2.30.32 (same error and the same SDK stack frames as yours) and on the latest version, 2.57.5. Your class follows the documented requirements for immutable classes: "Every method that is both not an override of Object.class and has not been annotated with @DynamoDbIgnore must be a getter for an attribute of the DynamoDB table."

    Because of @DynamoDbIgnore, the SDK skips getName(), but it still checks the generated builder, which has name(String). The SDK expects every builder method to have a matching getter, so it throws while building the table schema, before any request is sent to DynamoDB. I get the same error with a Lombok builder and with a hand-written builder, so it isn't specific to Immutables. There's an open pull request for this: #7409.

    Until a fix is released, you can point @DynamoDbImmutable(builder = ...) at a small builder that only has the attributes you store, and keep using the generated CustomerBuilder everywhere else. Your imports, @Value.Immutable and @Value.Style stay the same:

    @DynamoDbImmutable(builder = Customer.DdbBuilder.class)
    public interface Customer {
    
      @DynamoDbPartitionKey
      String getCustomerId();
    
      @Value.Default
      @DynamoDbIgnore
      default String getName() {
        return "";
      }
    
      // Used only by the Enhanced Client: one method per attribute stored in DynamoDB
      final class DdbBuilder {
        private final CustomerBuilder delegate = new CustomerBuilder();
    
        public DdbBuilder customerId(String customerId) {
          delegate.customerId(customerId);
          return this;
        }
    
        public Customer build() {
          return delegate.build();
        }
      }
    }

    I tested this against DynamoDB with SDK 2.30.32 and 2.57.5 (Java 17): putItem and getItem work, and only customerId is stored. A name you set in your code isn't saved, so items read from DynamoDB get the default "". Once a fix is released, you can remove DdbBuilder and point back to CustomerBuilder. Another option is to define the schema in code with StaticImmutableTableSchema. I have reached out to the team for the this issue & fix and I will update you here.

    Let me know if this works for you.

  6. added
    p2This is a standard priority issue
    and removed
    needs-triageThis issue or PR still needs to be triaged.
    on Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugThis issue is a bug.dynamodb-enhancedp2This is a standard priority issue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions