{"id":16782561,"url":"https://github.com/davidmoten/aws-lightweight-client-java","last_synced_at":"2025-03-17T02:31:43.150Z","repository":{"id":38325583,"uuid":"370843486","full_name":"davidmoten/aws-lightweight-client-java","owner":"davidmoten","description":"A lightweight java client for the AWS API. Signs requests with AWS Version 4 and offers helpful builders.","archived":false,"fork":false,"pushed_at":"2025-03-05T02:55:46.000Z","size":778,"stargazers_count":31,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-05T03:28:32.688Z","etag":null,"topics":["aws","aws-lambda","aws-sdk","client","java"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidmoten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-25T22:30:37.000Z","updated_at":"2025-03-05T02:55:42.000Z","dependencies_parsed_at":"2023-02-10T18:15:47.991Z","dependency_job_id":"b4f0e2e0-8722-4c1d-84b2-fad1a57638d7","html_url":"https://github.com/davidmoten/aws-lightweight-client-java","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Faws-lightweight-client-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Faws-lightweight-client-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Faws-lightweight-client-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Faws-lightweight-client-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidmoten","download_url":"https://codeload.github.com/davidmoten/aws-lightweight-client-java/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243841125,"owners_count":20356440,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-lambda","aws-sdk","client","java"],"created_at":"2024-10-13T07:45:52.935Z","updated_at":"2025-03-17T02:31:43.136Z","avatar_url":"https://github.com/davidmoten.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aws-lightweight-client-java\n\u003ca href=\"https://github.com/davidmoten/aws-lightweight-client-java/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/davidmoten/aws-lightweight-client-java/actions/workflows/ci.yml/badge.svg\"/\u003e\u003c/a\u003e\u003cbr/\u003e\n[![codecov](https://codecov.io/gh/davidmoten/aws-lightweight-client-java/branch/master/graph/badge.svg)](https://codecov.io/gh/davidmoten/aws-lightweight-client-java)\u003cbr/\u003e\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/aws-lightweight-client-java/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/aws-lightweight-client-java)\u003cbr/\u003e\n\nThis is a really lightweight standalone artifact (about 85K) that performs authentication (signing requests with AWS Signature Version 4) and helps you build requests against the AWS API. It includes nice concise builders, a lightweight inbuilt xml parser (to parse responses), an xml builder, and useful convenience methods. \n\nAside from cold-start runtime improvements in AWS Lambda, the small artifact size is presumably attractive for mobile device developers (Android especially). \n\n**Features**\n* small standalone artifact (75K)\n* concise fluent api\n* signs requests with AWS Signature Version 4\n* generates presigned urls\n* supports throwing custom exceptions\n* metadata and attributes support\n* xml response parsing support\n* xml builder\n* 100% unit test coverage\n* reduces average Lambda cold start time significantly\n* S3 Multipart upload [helper](https://github.com/davidmoten/aws-lightweight-client-java/wiki/Recipes#multipart-upload-a-file)\n* SnapStart support (reacts to environment variables)\n\n**Status**: released to [Maven Central](https://search.maven.org/artifact/com.github.davidmoten/aws-lightweight-client-java)\n\nMaven [reports](https://davidmoten.github.io/aws-lightweight-client-java/index.html) including [javadocs](https://davidmoten.github.io/aws-lightweight-client-java/apidocs/index.html)\n\nFor example with the 85K standalone artifact you can download an object from an S3 bucket:\n\n```java\nClient s3 = Client.s3()\n  .region(\"ap-southeast-2\")\n  .accessKey(accessKey)\n  .secretKey(secretKey)\n  .build();\n\nString content = s3\n  .path(\"myBucket\", \"myObject.txt\")\n  .responseAsUtf8();\n```\n\nHere's how to create an SQS queue and send a message to that queue. This time we'll create our Client for use in a Lambda handler (credentials are picked up from environment variables):\n```java\nClient sqs = Client.sqs().defaultClient().build();\n  \nString queueUrl = sqs\n    .query(\"Action\", \"CreateQueue\")\n    .query(\"QueueName\", queueName(applicationName, key))\n    .responseAsXml()\n    .content(\"CreateQueueResult\", \"QueueUrl\");\n    \nsqs.url(queueUrl) \n    .query(\"Action\", \"SendMessage\") \n    .query(\"MessageBody\", \"hi there\") \n    .execute();\n```\n\nHere's how to upload a file to an S3 bucket using multipart:\n```java\nMultipart \n  .s3(s3)\n  .bucket(\"mybucket\")\n  .key(\"mykey\")\n  .upload(file);\n```\n\nSee [Recipes](https://github.com/davidmoten/aws-lightweight-client-java/wiki/Recipes) for many more examples.\n\n## Lambda performance\nYou can see that usage is still pretty concise compared to using the AWS SDK v1 or v2 for Java. There's a significant advantage in using the lightweight client in a Java Lambda. \n\nThe test Lambda that I used does this:\n* puts a 240B object into an S3 bucket with metadata\n* creates an SQS queue \n* sends the queue a small message (16 bytes).\n\nUsing AWS SDK v1 the shaded minimized jar deployed to Lambda is 5.1MB (7.2MB unminimized), with AWS SDK v2 unminimized jar is 6.9MB (couldn't figure out the shade rules to minimize!) and with *aws-lightweight-client-java* the jar is 80K.\n\nThe conclusion from the comparison is that with this scenario Lambdas using *aws-lightweight-client* run their cold-start on average in **40% of the time** as using AWS SDK v1, **45% of the time** as using AWS SDK v2. Not only that but there does seem be a minor advantage in warm runtime (~10% faster).\n\n\u003cimg width=\"500\" src=\"src/docs/graph.jpeg\"/\u003e\n\nHere are the comparison details:\n\nI took the AWS SDK v1 and Lightweight lambdas and tested them with different memory allocations. The configured memory also affects the CPU allocation. At 2GB memory a full VCPU is allocated and CPU allocation is proportional to memory allocation.\n\n**Cold Start Runtimes (average)**\n\n| Memory | SDK v1 | Lightweight |\n|--------|-----|-------------|\n| 128MB  | Metaspace error | 19s |\n| 256MB  | 21s             | 8.1s |\n| 512MB  | 10.5s           | 3.9s |\n| 2GB    | 2.8s           | 1.0s |\n\n**Warm Runtimes (average)**\n\n| Memory | SDK v1 | Lightweight |\n|--------|-----|-------------|\n| 128MB  | Metaspace error | 2.4s |\n| 256MB  | 0.6s             | 0.5s |\n| 512MB  | 0.3s           | 0.3s |\n| 2GB    | 0.1s           | 0.1s |\n\nExcept for the 2GB case I measured cold-start runtimes several times and then 5-10 or so warm runtimes for each case. Much more data was gathered for the 2GB case below. \n\nNote that for AWS SDK v2 I followed the coding recommendations of https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/lambda-optimize-starttime.html. One exception to the AWS advice is that client objects were created in the handler method rather than instantiated as static fields.\n\n**Lambda runtimes for 2GB Memory in seconds**\n\n|          | SDK v1 Cold| SDK v2 Cold |Lightweight Cold| SDK v1 Warm | SDK v2 Warm |Lightweight Warm |\n|----------|--------|------|-------|-------|-----|-----|\n| average | 2.772 | 2.289 |1.04 |0.116 |0.130| 0.101|\n| stdev   | 0.448 | 0.130 | 0.116 |0.017|0.016| 0.014|\n| max     | 4.315 | 2.941 |1.30 | ? | ? | ?|\n| min     | 2.471 | 1.976 | 0.91 | 0.057 | 0.068 | 0.048 | \n| samples | 24 | 30 | 25 | 216 | 270 | 225 |\n\nNote that testing shows that using *com.amazonaws:aws-java-sdk-s3:1.11.1032* getting an object from an S3 bucket requires loading of 4203 classes yet using *aws-lightweight-client-java:0.1.3* requires loading of 2350 classes (56%). Using the AWS SDK v2 *software.amazon.awssdk:s3:2.16.78* still uses 3639 classes.\n\n### Instantiating client objects as static fields\nOne optimization suggested by AWS advice is to instantiate client objects (like `AwsS3Client`) in static fields so that the creation of the handler object brings about the once-only instantiation of the client objects. This doesn't necessarily have much of an effect on cold start time in terms of the total cold-start request time to the lambda but it does affect the billable runtime (it reduces it a lot). AWS charges for the runtime of the handler method call and the instantiation of the handler object is not part of that. Thus the reasonably lengthy period of class loading that happens on instantiation of the client objects is associated with the initialization phase of the lambda and is outside the billable runtime.\n\nI ran three lambdas once an hour (cold-start) and 10 times in succession immediately after the cold-start (warm invocations) and gathered some stats over a 28+ hour period. The source code for the three lambdas are below (everything is there for the full integration including cloudformation.yaml and deployment scripts):\n\n* AWS SDK v1 [handler](https://github.com/davidmoten/one-time-link-aws/blob/f3a11547c187216e2e1477d27726a3432348a73a/src/main/java/com/github/davidmoten/onetimelink/lambda/Handler.java) (`store` resource path)\n* AWS SDK v2 [handler](https://github.com/davidmoten/one-time-link-aws/blob/891e09ecc2c4019d00c33993be39098b8e91bfc4/src/main/java/com/github/davidmoten/onetimelink/lambda/Handler.java) (`store` resource path)\n* Lightweight client [handler](https://github.com/davidmoten/one-time-link-aws/blob/1.0.19/src/main/java/com/github/davidmoten/onetimelink/lambda/Handler.java) (`store` resource path)\n\nWhen you want to gather some statistics about the initialization phase as well as the billable runtime then you need to enable trace logging and the AWS XRay service to explore them. Unfortunately mucking about with XRay and trace logging is a bit painful when you want to look at longer than 6 hours so I've opted for another approach where I simply measure the full response time for an API Gateway + Lambda integration (I did leave trace logging and xray enabled though for analysis later if I get around to it).\n\n**Cold start request times (seconds) API Gateway + Lambda 2GB Memory**\n\n| | Average | Stdev | Min | Max | n |\n|-------|-------|-------|------|-------|------|\n| **AWS SDK v1** | 3.987 | 0.320 | 3.583 | 5.280 | 28 |\n| **AWS SDK v2** | 3.153 | 0.267 | 2.918 | 4.060 | 28 |\n| **lightweight** | 1.938 | 0.149 | 1.739 | 2.376 | 28 |\n\nNote that these requests were made from my not-very-snappy home internet connection. The deltas are informative though given all requests had the same payload and response body.\n\nTODO warm invocation analysis\n\n## Getting started\nAdd this dependency to your pom.xml:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.davidmoten\u003c/groupId\u003e\n    \u003cartifactId\u003eaws-lightweight-client-java\u003c/artifactId\u003e\n    \u003cversion\u003eVERSION_HERE\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Usage\n\nTo perform actions against the API you do need to know what methods exist and the parameters for those methods. This library is lightweight because it doesn't include a mass of generated classes from the API so you'll need to check the AWS API documentation to get that information. For example the API docs for S3 is [here](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html).\n\n### Creating a Client\nIn a Lambda handler environment variables hold the credentials and session token (if SnapStart active then credentials will be obtained from an endpoint defined in an environment variable). To pick those values up:\n\n```java\nClient s3 = Client.s3().defaultClient().build();\n```\nOutside of lambda you might specify your credentials explicitly:\n\n```java\nClient s3 = Client\n  .s3()\n  .region(\"ap-southeast-2\")\n  .accessKey(accessKey)\n  .secretKey(secretKey)\n  .build()\n```\nThere are a number of other options that can be set when building the Client:\n\n```java\nClient iam = Client\n  .serviceName(\"iam\") \n  .region(region) \n  .accessKey(accessKey)\n  .secretKey(secretKey)\n  .exceptionFactory(myExceptionFactory)\n  .exception(\n      x -\u003e !x.isOk() \u0026\u0026 x.contentUtf8().contains(\"NonExistentPolicy\"),\n      x -\u003e new PolicyDoesNotExistException(x.contentUtf8()))\n  .httpClient(myHttpClient) \n  .baseUrlFactory((service, region) -\u003e \"https://me.com/\")\n  .connectTimeout(30000, TimeUnit.MILLISECONDS)\n  .readTimeout(120000, TimeUnit.MILLISECONDS)\n  .build();\n```\nA client can be copied from another client to pick up same configuration (but with a different service name):\n\n```java\nClient sqs = Client.from(iam).build();\n```\n### Timeouts\nTimeouts can be set in the client builder and also for each request. Here's an example:\n\n```java\nClient s3 = Client\n  .s3() \n  .defaultClient()\n  .connectTimeout(30, TimeUnit.SECONDS)\n  .readTimeout(60, TimeUnit.SECONDS)\n  .build();\n    \n String content = s3\n  .path(\"myBucket\", \"myObject.txt\")\n  .connectTimeout(5, TimeUnit.SECONDS)\n  .readTimeout(5, TimeUnit.SECONDS)\n  .responseAsUtf8();\n```\n### Retries\nAutomatic retries can be configured in the client builder and also for each request including multipart requests. Capped\nexponential backoff is supported as is jitter (randomised intervals).\n\nDefault behaviour (that can be overridden) is to retry these HTTP status codes:\n\n```\n400, 403, 429, 500, 502, 503, 509\n```\nWhen the http client throws an exception it is retried if it is an `IOException` or an `UncheckedIOException`.\n\nDefault values for retries are:\n\n| Parameter | Default           |\n| ------------- |-------------:|\n| Max Attempts | 4 |\n| Initial Interval | 100ms      |\n| Exponential Backoff Factor | 2      |\n| Max Interval | 20s      |\n| Jitter | 0 (none) |\n\nThe retry interval after attempt N is calculated like this:\n```\ninterval = initialInterval * (backoffFactor ^ (N - 1)) * (1 - jitter * Math.random())\n```\n\nFor example, using the defaults the retry intervals would be 100ms, 200ms, 400ms and then failure would be propagated. If you don't want exponential backoff then set that parameter to 1. \n\n```java\nClient s3 = Client\n  .s3()\n  .defaultClient()\n  .retryMaxAttempts(10)\n  .retryInitialInterval(100, TimeUnit.MILLISECONDS)\n  .retryBackoffFactor(2.0)\n  .retryMaxInterval(30, TimeUnit.SECONDS)\n  .retryJitter(0.5)\n  .retryStatusCodes(400, 403, 429, 500, 502, 503)\n  .retryException(e -\u003e false) // never retry exceptions\n  .build();\n```\nMost of the same options are available on request builders:\n```java\nString content = s3\n  .path(\"myBucket\", \"myObject.txt\")\n  .connectTimeout(5, TimeUnit.SECONDS)\n  .readTimeout(5, TimeUnit.SECONDS)\n  .retryMaxAttempts(3)\n  .retryInitialInterval(5, TimeUnit.SECONDS)\n  .responseAsUtf8();\n```\nYou can also completely control the request retry (when there is an HTTP status code) via these builder methods:\n```java\nClient s3 = Client\n  .s3()\n  .defaultClient()\n  .retryCondition(ris -\u003e ris.statusCode() == 500)\n  .retryException(e -\u003e e instanceof IOException \n                       || e instanceof UncheckedIOException)\n...\n```\n\n### Presigned URLs\nPresigned URLs are generated as follows (with a specified expiry duration):\n\n```java\nString presignedUrl = \n  s3\n    .path(bucketName, objectName) \n    .presignedUrl(1, TimeUnit.DAYS));\n```\n\n### S3\nThe code below demonstrates the following:\n* create bucket\n* put object with metadata\n* read object and metadata\n* list objects in bucket\n* delete object\n* delete bucket\n\n```java\n// we'll create a random bucket name\nString bucketName = \"temp-bucket-\" + System.currentTimeMillis();\n\n///////////////////////\n// create bucket\n///////////////////////\n\nString createXml = Xml\n    .create(\"CreateBucketConfiguration\")\n    .a(\"xmlns\", \"http://s3.amazonaws.com/doc/2006-03-01/\")\n    .e(\"LocationConstraint\").content(region)\n    .toString();       \ns3.path(bucketName)\n    .method(HttpMethod.PUT)\n    .requestBody(createXml)\n    .execute();\n\n////////////////////////////\n// put object with metadata\n///////////////////////////\n\nString objectName = \"ExampleObject.txt\";\ns3\n    .path(bucketName, objectName)\n    .method(HttpMethod.PUT)\n    .requestBody(\"hi there\")\n    .metadata(\"category\", \"something\")\n    .execute();\n\n///////////////////////////////////\n// read object including metadata\n///////////////////////////////////\n\nString text = s3\n    .path(bucketName + \"/\" + objectName)\n    .responseAsUtf8();\n\n///////////////////////////////////\n// read object\n///////////////////////////////////\n\nResponse r = s3\n    .path(bucketName, objectName)\n    .response();\nSystem.out.println(\"response ok=\" + response.isOk());\nSystem.out.println(r.content().length + \" chars read\");\nSystem.out.println(\"category=\" + r.metadata(\"category\").orElse(\"\"));\n\n///////////////////////////////////\n// list bucket objects \n///////////////////////////////////\n\nList\u003cString\u003e keys = s3\n    .url(\"https://\" + bucketName + \".s3.\" + region + \".amazonaws.com\")\n    .query(\"list-type\", \"2\")\n    .responseAsXml()\n    .childrenWithName(\"Contents\")\n    .stream()\n    .map(x -\u003e x.content(\"Key\"))\n    .collect(Collectors.toList());\nSystem.out.println(keys);\n\n///////////////////////////////////\n// delete object \n///////////////////////////////////\n\ns3.path(bucketName, objectName) \n    .method(HttpMethod.DELETE) \n    .execute();\n        \n///////////////////////////////////\n// delete bucket \n///////////////////////////////////\n\ns3.path(bucketName) \n\t.method(HttpMethod.DELETE) \n\t.execute();\n```\n\n### SQS\nHere are some SQS tasks:\n\n* create an sqs queue\n* get the queue url\n* place two messages on the queue\n* read all messages of the queue and mark them as read\n* delete the sqs queue\n\nYou'll note that most of the interactions with sqs involve using the url of the queue rather than the base service endpoint (`http://sqs.amazonaws.com`).\n\n```java\nString queueName = \"MyQueue-\" + System.currentTimeMillis();\n\n///////////////////////////////////\n// create queue\n///////////////////////////////////\n\nsqs.query(\"Action\", \"CreateQueue\") \n    .query(\"QueueName\", queueName) \n    .execute();\n\n///////////////////////////////////\n// get queue url\n///////////////////////////////////\n\nString queueUrl = sqs \n    .query(\"Action\", \"GetQueueUrl\") \n    .query(\"QueueName\", queueName) \n    .responseAsXml() \n    .content(\"GetQueueUrlResult\", \"QueueUrl\");\n\n///////////////////////////////////\n// send a message\n///////////////////////////////////\n\nsqs.url(queueUrl) \n    .query(\"Action\", \"SendMessage\") \n    .query(\"MessageBody\", \"hi there\") \n    .execute();\n\n///////////////////////////////////\n// read all messages\n///////////////////////////////////\n\nList\u003cXmlElement\u003e list;\ndo {\n    list = sqs.url(queueUrl)\n        .query(\"Action\", \"ReceiveMessage\")\n        .responseAsXml()\n        .child(\"ReceiveMessageResult\")\n        .children();\n\n    list.forEach(x -\u003e {\n\t    String msg = x.child(\"Body\").content();\n\t    System.out.println(msg);\n\t    // mark message as read\n\t    sqs.url(queueUrl)\n\t            .query(\"Action\", \"DeleteMessage\")\n\t            .query(\"ReceiptHandle\", x.child(\"ReceiptHandle\").content())\n\t            .execute();\n    });\n} while (!list.isEmpty());\n\n///////////////////////////////////\n// delete queue\n///////////////////////////////////\n\nsqs.url(queueUrl) \n    .query(\"Action\", \"DeleteQueue\") \n    .execute();\n```\n\n### Recipes\nSee [Recipes](https://github.com/davidmoten/aws-lightweight-client-java/wiki/Recipes) for many more examples.\n\n### Attributes\nSome of the AWS API services (like SQS) represent property maps in the query string like this `?Attribute.Name.1=size\u0026Attribute.Value.1=large\u0026Attribute.Name.2=color\u0026Attribute.Value.2=red`. The request builder has helper methods to do this for you:\n\n```java\n// Create a FIFO queue\nString queueUrl = sqs.query(\"Action\", \"CreateQueue\") \n  .query(\"QueueName\", queueName(applicationName, key)) \n  .attribute(\"FifoQueue\", \"true\") \n  .attribute(\"ContentBasedDeduplication\", \"true\") \n  .attribute(\"MessageRetentionPeriod\", String.valueOf(TimeUnit.DAYS.toSeconds(14))) \n  .attribute(\"VisibilityTimeout\", \"30\") \n  .responseAsXml() \n  .content(\"CreateQueueResult\", \"QueueUrl\");\n```\n\nWhen the prefix of the attribute is different, say \"MessageProperty\" instead of \"Attribute\" then you can use the `.attributePrefix(String)` method before calling `.attribute(String)`.\n\n### Metadata\nTo set a header `x-amz-meta-KEY:VALUE` use the builder method `.metadata(KEY, VALUE)`.\n\n### Error handling\nLet's look at a simple one, reading an object in an S3 bucket.\n\n```java\nString text = s3\n    .path(bucketName + \"/\" + objectName)\n    .responseAsUtf8();\n```\nIf the object does not exist an exception will be thrown like this:\n```\ncom.github.davidmoten.aws.lw.client.ServiceException: statusCode=404: \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cError\u003e\u003cCode\u003eNoSuchKey\u003c/Code\u003e\u003cMessage\u003eThe specified key does not exist.\u003c/Message\u003e\u003cKey\u003enot-there\u003c/Key\u003e\u003cRequestId\u003e1TVAXX4VF5DYHJJH\u003c/RequestId\u003e\u003cHostId\u003eVrvGCPhExKbjuONSuX/LGw0mYSndjg3t26LNAQCKTL/i5U+cZfYa4ow3KQ1tpJdQuMH9sB4JTUk=\u003c/HostId\u003e\u003c/Error\u003e\n\tat com.github.davidmoten.aws.lw.client.internal.ExceptionFactoryDefault.create(ExceptionFactoryDefault.java:17)\n\tat com.github.davidmoten.aws.lw.client.Request.responseAsBytes(Request.java:140)\n\tat com.github.davidmoten.aws.lw.client.Request.responseAsUtf8(Request.java:153)\n\tat com.github.davidmoten.aws.lw.client.ClientMain.main(ClientMain.java:48)\n```\n\nYou can see that the AWS exception message (in xml format) is present in the error message and can be used to check for standard codes. If you were using the full AWS SDK library then it would throw a `NoSuchKeyException`. In our case we check for the presence of `NoSuchKey` in the error message.\n\nThe code below does not throw an exception when the object does not exist. However, `response.isOk()` returns false:\n\n```java\nResponse r = s3\n    .path(bucketName + \"/\" + objectName)\n    .response();\nSystem.out.println(\"ok=\" + r.isOk() + \", statusCode=\" + r.statusCode() + \", message=\" + r.contentUtf8());\n```\n\nThe output is:\n```\nok=false, statusCode=404, message=\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cError\u003e\u003cCode\u003eNoSuchKey\u003c/Code\u003e\u003cMessage\u003eThe specified key does not exist.\u003c/Message\u003e\u003cKey\u003enotThere\u003c/Key\u003e\u003cRequestId\u003e4AAX24QZ8777FA6B\u003c/RequestId\u003e\u003cHostId\u003e4N1rsMjjdM7tjKSQDXNQZNH8EOqNckUsO6gRVPfcjMmHZ9APRwYJwufZOr9l1Qlinux5W537bDc=\u003c/HostId\u003e\u003c/Error\u003e\n```\n### Custom exceptions\nYou can define what exceptions get thrown using a builder method for a `Client`:\n\n```java\nClient sqs = Client \n    .sqs()\n    .defaultClient()\n    .exception(\n            x -\u003e !x.isOk() \u0026\u0026 x.contentUtf8().contains(\"NonExistentQueue\"),\n            x -\u003e new QueueDoesNotExistException(x.contentUtf8())\n    .build()\n```\nYou can add multiple exception handlers like above or you can set an `ExceptionFactory`. Any response not matching the criteria will \nthrow a `ServiceException` (in those circumstances where exceptions are thrown, like `.responseAsBytes()`, `.responseAsUtf8()` and `.responseAsXml()`).\n\n## TODO\n* Can a faster cold-start be had using Bouncy Castle TLS library?\n* add debug logging?\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmoten%2Faws-lightweight-client-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidmoten%2Faws-lightweight-client-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmoten%2Faws-lightweight-client-java/lists"}