Repository: getmoto/moto

Issue:
[DynamoDb] All Errors are deserialized as DynamoDbException in JAVA SDK
Using MotoServer to test Java based services. This Service creates the DynamoDb table if it doesn't exist, ie, on ResourceNotFoundException, table is created.
When using the MotoServer, We get response

```json
{"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException", "message": "Requested resource not found"}
```
When this is deserialized in java sdk, as there is no error-code for  `com.amazonaws.dynamodb.v20111205#ResourceNotFoundException` hence, All the error is wrapped, and returned as `DynamoDbException`

With other services, similar issue is not occurring. The issue exists for version>=3.1.5, and seems to be introduced by this https://github.com/getmoto/moto/pull/5012
Tried using <=3.1.4 to overcome this problem, but ended up encountering another bug related to Glue, which is fixed in the recent version. So, unable to use the older versions as a workaround

Could you please share reason behind the error message? would it be possible to have error message compatible with Java SDKs too?



Discussion:
Thanks for raising this @munendrasn - marking it as a bug.

#5012 was an attempt to streamline all error messages - but because we only fully test against the Python SDK, which is more forgiving, we never realized it was broken. Will raise a PR shortly.
@bblommers 
Thanks for the quick fix. 

I did a quick test with `4.1.4.dev29` but encountering the same issue with Java SDK (2.17.257), ie, even though the error message proper, `Requested resource not found: Table: e9f18915466b4516adf373ae754a19ed not found` but error/exception of type DynamoDbException instead of ResourceNotFoundException

I think having the error prefix is causing the issue. Based on the description from the PR https://github.com/getmoto/moto/pull/5997#issue-1601974124, it seems prefix is required for python sdk. Is this the correct understanding?

@munendrasn I actually tested this against AWS, and this format is exactly what AWS returns. So I assumed the Java SDK would be OK with it.
I'll have another look.

Resolving patch:
diff --git a/moto/core/exceptions.py b/moto/core/exceptions.py
--- a/moto/core/exceptions.py
+++ b/moto/core/exceptions.py
@@ -71,10 +71,14 @@ def get_headers(
         self, *args: Any, **kwargs: Any  # pylint: disable=unused-argument
     ) -> List[Tuple[str, str]]:
         return [
-            ("X-Amzn-ErrorType", self.error_type or "UnknownError"),
+            ("X-Amzn-ErrorType", self.relative_error_type or "UnknownError"),
             ("Content-Type", self.content_type),
         ]
 
+    @property
+    def relative_error_type(self) -> str:
+        return self.error_type
+
     def get_body(
         self, *args: Any, **kwargs: Any  # pylint: disable=unused-argument
     ) -> str:
@@ -95,6 +99,12 @@ def __init__(
         )
         self.content_type = "application/json"
 
+    @property
+    def relative_error_type(self) -> str:
+        # https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html
+        # If a # character is present, then take only the contents after the first # character in the value
+        return self.error_type.split("#")[-1]
+
     def get_body(self, *args: Any, **kwargs: Any) -> str:
         return self.description
 
diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py
--- a/moto/dynamodb/exceptions.py
+++ b/moto/dynamodb/exceptions.py
@@ -315,9 +315,9 @@ def __init__(self, target_table_name: str):
 
 
 class ResourceInUseException(JsonRESTError):
-    def __init__(self) -> None:
+    def __init__(self, msg: Optional[str] = None) -> None:
         er = ERROR_TYPE_PREFIX + "ResourceInUseException"
-        super().__init__(er, "Resource in use")
+        super().__init__(er, msg or "Resource in use")
 
 
 class StreamAlreadyEnabledException(JsonRESTError):
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -60,7 +60,7 @@ def default_vpc_endpoint_service(
 
     def create_table(self, name: str, **params: Any) -> Table:
         if name in self.tables:
-            raise ResourceInUseException
+            raise ResourceInUseException(f"Table already exists: {name}")
         table = Table(
             name, account_id=self.account_id, region=self.region_name, **params
         )


Test patch:
diff --git a/other_langs/tests_java/pom.xml b/other_langs/tests_java/pom.xml
new file mode 100644
--- /dev/null
+++ b/other_langs/tests_java/pom.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>moto.tests</groupId>
+  <artifactId>java-tests</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <name>java-tests</name>
+  <url>docs.getmoto.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+  </properties>
+
+  <dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>bom</artifactId>
+      <version>2.20.14</version>
+      <type>pom</type>
+      <scope>import</scope>
+    </dependency>
+  </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.11</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+    <groupId>software.amazon.awssdk</groupId>
+    <artifactId>dynamodb</artifactId>
+  </dependency>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>s3</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>software.amazon.awssdk</groupId>
+          <artifactId>netty-nio-client</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>software.amazon.awssdk</groupId>
+          <artifactId>apache-client</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>url-connection-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>apache-client</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
+      <plugins>
+        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
+        <plugin>
+          <artifactId>maven-clean-plugin</artifactId>
+          <version>3.1.0</version>
+        </plugin>
+        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+        <plugin>
+          <artifactId>maven-resources-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.8.0</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.22.1</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-install-plugin</artifactId>
+          <version>2.5.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>2.8.2</version>
+        </plugin>
+        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
+        <plugin>
+          <artifactId>maven-site-plugin</artifactId>
+          <version>3.7.1</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-project-info-reports-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
diff --git a/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java b/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java
new file mode 100644
--- /dev/null
+++ b/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java
@@ -0,0 +1,51 @@
+package moto.tests;
+
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.http.SdkHttpConfigurationOption;
+import software.amazon.awssdk.http.apache.ApacheHttpClient;
+import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.utils.AttributeMap;
+
+import java.net.URI;
+import java.time.Duration;
+
+/**
+ * The module containing all dependencies required by the {@link Handler}.
+ */
+public class DependencyFactory {
+
+    private static final URI MOTO_URI = URI.create("http://localhost:5000");
+
+    private DependencyFactory() {}
+
+    /**
+     * @return an instance of S3Client
+     */
+    public static S3Client s3Client() {
+        return S3Client.builder()
+                .region(Region.US_EAST_1)
+                .httpClientBuilder(ApacheHttpClient.builder())
+                .endpointOverride(MOTO_URI)
+                .build();
+    }
+
+    public static DynamoDbClient dynamoClient() {
+        final AttributeMap attributeMap = AttributeMap.builder()
+                .put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, true)
+                .build();
+        // Not in use at the moment, but useful for testing
+        SdkHttpClient proxy_client = UrlConnectionHttpClient.builder()
+                .socketTimeout(Duration.ofMinutes(1))
+                .proxyConfiguration(proxy -> proxy.endpoint(URI.create("http://localhost:8080")))
+                .buildWithDefaults(attributeMap);
+        return DynamoDbClient.builder()
+                .region(Region.US_EAST_1)
+                // .httpClient(client)
+                .httpClientBuilder(ApacheHttpClient.builder())
+                .endpointOverride(MOTO_URI)
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/other_langs/tests_java/src/main/java/moto/tests/DynamoLogic.java b/other_langs/tests_java/src/main/java/moto/tests/DynamoLogic.java
new file mode 100644
--- /dev/null
+++ b/other_langs/tests_java/src/main/java/moto/tests/DynamoLogic.java
@@ -0,0 +1,42 @@
+package moto.tests;
+
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
+import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
+import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
+import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
+import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
+import software.amazon.awssdk.services.dynamodb.model.KeyType;
+import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
+
+public class DynamoLogic {
+
+    private final DynamoDbClient client;
+    public DynamoLogic() {
+        client = DependencyFactory.dynamoClient();
+    }
+
+    public String createTable(String tableName, String key) {
+        CreateTableRequest request = CreateTableRequest.builder()
+                .attributeDefinitions(AttributeDefinition.builder()
+                        .attributeName(key)
+                        .attributeType(ScalarAttributeType.S)
+                        .build())
+                .keySchema(KeySchemaElement.builder()
+                        .attributeName(key)
+                        .keyType(KeyType.HASH)
+                        .build())
+                .provisionedThroughput(ProvisionedThroughput.builder()
+                        .readCapacityUnits(new Long(10))
+                        .writeCapacityUnits(new Long(10))
+                        .build())
+                .tableName(tableName)
+                .build();
+        System.out.println("Prepared CreateTableRequest");
+
+        CreateTableResponse response = this.client.createTable(request);
+        System.out.println(response.tableDescription());
+
+        return response.tableDescription().tableName();
+    }
+}
diff --git a/other_langs/tests_java/src/main/java/moto/tests/Handler.java b/other_langs/tests_java/src/main/java/moto/tests/Handler.java
new file mode 100644
--- /dev/null
+++ b/other_langs/tests_java/src/main/java/moto/tests/Handler.java
@@ -0,0 +1,83 @@
+package moto.tests;
+
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+
+
+/**
+ * Example class as provided by AWS - verify that basic S3 functions work
+ */
+public class Handler {
+    private final S3Client s3Client;
+
+    public Handler() {
+        s3Client = DependencyFactory.s3Client();
+    }
+
+    public void sendRequest() {
+        String bucket = "bucket" + System.currentTimeMillis();
+        String key = "key";
+
+        tutorialSetup(s3Client, bucket);
+
+        System.out.println("Uploading object...");
+
+        s3Client.putObject(PutObjectRequest.builder().bucket(bucket).key(key)
+                        .build(),
+                RequestBody.fromString("Testing with the {sdk-java}"));
+
+        System.out.println("Upload complete");
+        System.out.printf("%n");
+
+        cleanUp(s3Client, bucket, key);
+
+        System.out.println("Closing the connection to {S3}");
+        s3Client.close();
+        System.out.println("Connection closed");
+        System.out.println("Exiting...");
+    }
+
+    public static void tutorialSetup(S3Client s3Client, String bucketName) {
+        try {
+            s3Client.createBucket(CreateBucketRequest
+                    .builder()
+                    .bucket(bucketName)
+                    .build());
+            System.out.println("Creating bucket: " + bucketName);
+            s3Client.waiter().waitUntilBucketExists(HeadBucketRequest.builder()
+                    .bucket(bucketName)
+                    .build());
+            System.out.println(bucketName + " is ready.");
+            System.out.printf("%n");
+        } catch (S3Exception e) {
+            System.err.println(e.awsErrorDetails().errorMessage());
+            System.exit(1);
+        }
+    }
+
+    public static void cleanUp(S3Client s3Client, String bucketName, String keyName) {
+        System.out.println("Cleaning up...");
+        try {
+            System.out.println("Deleting object: " + keyName);
+            DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(bucketName).key(keyName).build();
+            s3Client.deleteObject(deleteObjectRequest);
+            System.out.println(keyName + " has been deleted.");
+            System.out.println("Deleting bucket: " + bucketName);
+            DeleteBucketRequest deleteBucketRequest = DeleteBucketRequest.builder().bucket(bucketName).build();
+            s3Client.deleteBucket(deleteBucketRequest);
+            System.out.println(bucketName + " has been deleted.");
+            System.out.printf("%n");
+        } catch (S3Exception e) {
+            System.err.println(e.awsErrorDetails().errorMessage());
+            System.exit(1);
+        }
+        System.out.println("Cleanup complete");
+        System.out.printf("%n");
+    }
+}
\ No newline at end of file
diff --git a/other_langs/tests_java/src/test/java/moto/tests/DynamoTest.java b/other_langs/tests_java/src/test/java/moto/tests/DynamoTest.java
new file mode 100644
--- /dev/null
+++ b/other_langs/tests_java/src/test/java/moto/tests/DynamoTest.java
@@ -0,0 +1,31 @@
+package moto.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import software.amazon.awssdk.services.dynamodb.model.ResourceInUseException;
+
+public class DynamoTest {
+
+    @Test
+    public void testSingleTableExists() {
+        DynamoLogic logic = new DynamoLogic();
+        String table_name = logic.createTable("table", "key");
+
+        assertEquals("table", table_name);
+    }
+
+    @Test
+    public void testTableCannotBeCreatedTwice() {
+        DynamoLogic logic = new DynamoLogic();
+        // Going once...
+        logic.createTable("table2", "key");
+        try {
+            // Going twice should throw a specific exception
+            logic.createTable("table2", "key");
+        } catch (ResourceInUseException riue) {
+            assertTrue(riue.getMessage().startsWith("Table already exists: table2"));
+        }
+    }
+}
diff --git a/other_langs/tests_java/src/test/java/moto/tests/S3Test.java b/other_langs/tests_java/src/test/java/moto/tests/S3Test.java
new file mode 100644
--- /dev/null
+++ b/other_langs/tests_java/src/test/java/moto/tests/S3Test.java
@@ -0,0 +1,16 @@
+package moto.tests;
+
+import org.junit.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class S3Test
+{
+
+    @Test
+    public void connectToS3() {
+        Handler handler = new Handler();
+        handler.sendRequest();
+    }
+}
diff --git a/tests/test_dynamodb/test_server.py b/tests/test_dynamodb/test_server.py
--- a/tests/test_dynamodb/test_server.py
+++ b/tests/test_dynamodb/test_server.py
@@ -24,9 +24,7 @@ def test_table_list():
     res = test_client.post(
         "/", headers=headers, data=json.dumps({"TableName": "test-table2"})
     )
-    res.headers.should.have.key("X-Amzn-ErrorType").equals(
-        "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
-    )
+    res.headers.should.have.key("X-Amzn-ErrorType").equals("ResourceNotFoundException")
     body = json.loads(res.data.decode("utf-8"))
     body.should.equal(
         {