aws / aws-sdk-java-v2

The official AWS SDK for Java - Version 2
Apache License 2.0
2.16k stars 835 forks source link

DynamoDB Enhanced v. 2.15.41 putItem - getItem generates java.lang.NoClassDefFoundError #2198

Open Max161 opened 3 years ago

Max161 commented 3 years ago

When using DynamoDB version 2.15.41, but even the older versions, with the play framewok 2.8 (java) it throws a NoClassDefFoundError when you set the Table Schema via annotated class or via bean.

Describe the bug

I'm trying to create a simple registration/login application with the support of DynamoDB and Play Framework with java. I downloaded the last play java seed from the official site and added the dependencies for DynamoDb and DynamoDB enhanced v. 2.15.41.

I wrote some simple API's to call Create - Put - Get for the items. But I'm not able to put or get items from the table

What I mean is that if I define a table as following it will throw a NoClassDefFound when used:

DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbAsyncClient
    .builder()
    .endpointOverride(URI.create("http://localhost:8000"))
    .build();

DynamoDbEnhancedAsyncClient enhancedAsyncClient = DynamoDbEnhancedAsyncClient
    .builder()
    .dynamoDbClient(dynamoDbAsyncClient)
    .build();

return enhancedAsyncClient.table("Customer", TableSchema.fromClass(Customer.class));
 @DynamoDbBean
    public static class Customer {

        private String  id;
        private String  name;
        private String  email;
        private Instant regDate;

        @DynamoDbPartitionKey
        public String getId() { return this.id; }
        @DynamoDbSortKey
        public String getCustName() { return this.name; }
        //Omitted the getters and setters for brevity
    }

Expected Behavior

I should be able to put and get items correctly.

Current Behavior

When you run this code it's going to throw the exception.

  Key key = Key.builder()
                .partitionValue("id103")
                .sortValue("Susan Blue")
                .build();

        // Get the item by using the key
        return table.getItem(r -> r.key(key))
ERROR akka.actor.ActorSystemImpl akka.actor.ActorSystemImpl(application) Uncaught error from thread [application-akka.actor.default-dispatcher-6]: controllers/HomeController$Customer, shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[application]
java.lang.NoClassDefFoundError: controllers/HomeController$Customer
    at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ResolvedImmutableAttribute.lambda$create$0(ResolvedImmutableAttribute.java:48)
    at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.lambda$itemToMap$5(StaticImmutableTableSchema.java:491)
    at java.util.ArrayList.forEach(ArrayList.java:1259)
    at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1082)
    at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.itemToMap(StaticImmutableTableSchema.java:489)
    at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.itemToMap(WrappedTableSchema.java:59)
    at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.itemToMap(WrappedTableSchema.java:59)
    at software.amazon.awssdk.enhanced.dynamodb.internal.operations.PutItemOperation.generateRequest(PutItemOperation.java:71)
    at software.amazon.awssdk.enhanced.dynamodb.internal.operations.PutItemOperation.generateRequest(PutItemOperation.java:40)
    at software.amazon.awssdk.enhanced.dynamodb.internal.operations.CommonOperation.executeAsync(CommonOperation.java:139)
    at software.amazon.awssdk.enhanced.dynamodb.internal.operations.TableOperation.executeOnPrimaryIndexAsync(TableOperation.java:81)
    at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbAsyncTable.putItem(DefaultDynamoDbAsyncTable.java:177)
    at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbAsyncTable.putItem(DefaultDynamoDbAsyncTable.java:185)
    at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbAsyncTable.putItem(DefaultDynamoDbAsyncTable.java:190)
    at controllers.HomeController.putItem(HomeController.java:92)
    at router.Routes$$anonfun$routes$1.$anonfun$applyOrElse$8(Routes.scala:169)
    at play.core.routing.HandlerInvokerFactory$$anon$7.resultCall(HandlerInvoker.scala:146)
    at play.core.routing.HandlerInvokerFactory$JavaActionInvokerFactory$$anon$3$$anon$4$$anon$5.invocation(HandlerInvoker.scala:115)
    at play.core.j.JavaAction$$anon$1.call(JavaAction.scala:119)
    at play.http.DefaultActionCreator$1.call(DefaultActionCreator.java:33)
    at play.core.j.JavaAction.$anonfun$apply$8(JavaAction.scala:175)
    at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:671)
    at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:430)
    at play.core.j.HttpExecutionContext.$anonfun$execute$1(HttpExecutionContext.scala:64)
    at play.api.libs.streams.Execution$trampoline$.execute(Execution.scala:70)
    at play.core.j.HttpExecutionContext.execute(HttpExecutionContext.scala:59)
    at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
    at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
    at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:276)
    at scala.concurrent.impl.Promise$DefaultPromise.map(Promise.scala:146)
    at scala.concurrent.Future$.apply(Future.scala:671)
    at play.core.j.JavaAction.apply(JavaAction.scala:176)
    at play.api.mvc.Action.$anonfun$apply$4(Action.scala:82)
    at play.api.libs.streams.StrictAccumulator.$anonfun$mapFuture$4(Accumulator.scala:168)
    at scala.util.Try$.apply(Try.scala:210)
    at play.api.libs.streams.StrictAccumulator.$anonfun$mapFuture$3(Accumulator.scala:168)
    at scala.Function1.$anonfun$andThen$1(Function1.scala:85)
    at scala.Function1.$anonfun$andThen$1(Function1.scala:85)
    at scala.Function1.$anonfun$andThen$1(Function1.scala:85)
    at play.api.libs.streams.StrictAccumulator.run(Accumulator.scala:199)
    at play.core.server.AkkaHttpServer.$anonfun$runAction$4(AkkaHttpServer.scala:417)
    at akka.http.scaladsl.util.FastFuture$.strictTransform$1(FastFuture.scala:41)
    at akka.http.scaladsl.util.FastFuture$.$anonfun$transformWith$3(FastFuture.scala:51)
    at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:447)
    at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:56)
    at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:93)
    at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
    at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:94)
    at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:93)
    at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:48)
    at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:48)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175)
Caused by: java.lang.ClassNotFoundException: controllers.HomeController$Customer
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    ... 55 common frames omitted

Steps to Reproduce

A public repository with the code could be found here: https://github.com/Max161/AWS_DynamoDB_ClassNotFound_Error

To run the code use sbt run command in the main folder. Than you can use postman to call the API: /createtable /getItem /putItem

Your Environment

debora-ito commented 3 years ago

I'm not familiar with Play Framework, but my guess would be that Play's architecture (from what I read in their website) relies in Java reflection, and this may not work very well with DynamoDB Enhanced Client's underlying structure.

I'll investigate a little more, but right now I'm leaning towards changing this to a feature request.

Max161 commented 3 years ago

We've checked further for the issue and it seems out that DynamoDB, internally, is using Class.forName. As per this thread, it's not a good practice. It means that the library is looking for the class's definition and won't find it.

As a temporary workaround, you can take your dynamoDB models and include them as a separated library (jar). That way the classloader will find the right path to the class.

Hope you could fix it

u6f6o commented 3 years ago

Thx for providing a workaround @Max161. I must admit though, that it feels rather awkward to provide a separate jar for the models.

I hope this will be fixed soon 👍

Max161 commented 3 years ago

Thx for providing a workaround @Max161. I must admit though, that it feels rather awkward to provide a separate jar for the models.

I hope this will be fixed soon

Dear @u6f6o I know. I don't like it either. But, afaik, this is the only way to not be bothered by this bug atm.

rohit-gandhe commented 2 years ago

It's been a year and it's still an issue :(