{"id":15019320,"url":"https://github.com/jaidenashmore/java-dynamic-sqs-listener","last_synced_at":"2025-06-21T17:06:59.361Z","repository":{"id":33263126,"uuid":"143943773","full_name":"JaidenAshmore/java-dynamic-sqs-listener","owner":"JaidenAshmore","description":"Java SQS Listener library built to be customisable and dynamic during runtime","archived":false,"fork":false,"pushed_at":"2024-10-19T12:34:55.000Z","size":3991,"stargazers_count":56,"open_issues_count":28,"forks_count":13,"subscribers_count":5,"default_branch":"7.x","last_synced_at":"2025-06-06T05:04:58.943Z","etag":null,"topics":["java","kotlin","ktor","queue-listeners","spring-boot","sqs","sqs-listener","sqs-queue"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JaidenAshmore.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-08T01:12:36.000Z","updated_at":"2025-06-04T19:02:59.000Z","dependencies_parsed_at":"2024-10-10T05:31:21.718Z","dependency_job_id":"ae2f1e5d-a6f4-4155-b3fe-004c39201aed","html_url":"https://github.com/JaidenAshmore/java-dynamic-sqs-listener","commit_stats":{"total_commits":445,"total_committers":6,"mean_commits":74.16666666666667,"dds":0.6337078651685393,"last_synced_commit":"87d6e9e3dae674d8434bfe9bef8666b0429ad8bf"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/JaidenAshmore/java-dynamic-sqs-listener","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaidenAshmore%2Fjava-dynamic-sqs-listener","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaidenAshmore%2Fjava-dynamic-sqs-listener/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaidenAshmore%2Fjava-dynamic-sqs-listener/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaidenAshmore%2Fjava-dynamic-sqs-listener/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JaidenAshmore","download_url":"https://codeload.github.com/JaidenAshmore/java-dynamic-sqs-listener/tar.gz/refs/heads/7.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaidenAshmore%2Fjava-dynamic-sqs-listener/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261162066,"owners_count":23118221,"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":["java","kotlin","ktor","queue-listeners","spring-boot","sqs","sqs-listener","sqs-queue"],"created_at":"2024-09-24T19:53:19.832Z","updated_at":"2025-06-21T17:06:54.246Z","avatar_url":"https://github.com/JaidenAshmore.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Java Dynamic SQS Listener\n\n[![Build and Test](https://github.com/JaidenAshmore/java-dynamic-sqs-listener/workflows/Build%20and%20Test/badge.svg?branch=7.x)](https://github.com/JaidenAshmore/java-dynamic-sqs-listener/actions?query=workflow%3A%22Build+and+Test%22+branch%3A7.x)\n[![Coverage Status](https://coveralls.io/repos/github/JaidenAshmore/java-dynamic-sqs-listener/badge.svg?branch7.x)](https://coveralls.io/github/JaidenAshmore/java-dynamic-sqs-listener?branch=7.x)\n[![Maven Central](https://img.shields.io/maven-central/v/com.jashmore/java-dynamic-sqs-listener-api?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.jashmore%22%20AND%20%22java-dynamic-sqs-listener%22)\n\nThe Java Dynamic SQS Listener is a library that simplifies the listening of messages on an [AWS SQS queue](https://aws.amazon.com/sqs/). It has been\nbuilt from the ground up with the goal of making it framework agnostic, easily customisable and allow for dynamic changes to the configuration during runtime.\n\n## Getting Started\n\nThe following provides some examples using the library with different languages or frameworks.\n\n### Spring Boot Quick Guide\n\n1.  Include the dependency:\n\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n        \u003cartifactId\u003ejava-dynamic-sqs-listener-spring-starter\u003c/artifactId\u003e\n        \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n    ```\n\n    or\n\n    ```kotlin\n    dependencies {\n        implementation(\"com.jashmore:java-dynamic-sqs-listener-spring-starter:${sqs.listener.version}\")\n    }\n    ```\n\n1.  In one of your beans, attach a\n    [@QueueListener](./annotations/src/main/java/com/jashmore/sqs/annotations/core/basic/QueueListener.java)\n    annotation to a method indicating that it should process messages from a queue.\n\n    ```java\n    @Service\n    public class MyMessageListener {\n\n        // The queue here can point to your SQS server, e.g. a\n        // local SQS server or one on AWS\n        @QueueListener(\"${insert.queue.url.here}\")\n        public void processMessage(@Payload final String payload) {\n            // process the message payload here\n        }\n    }\n\n    ```\n\n    This will use any configured `SqsAsyncClient` in the application context for connecting to the queue, otherwise a default\n    will be provided that will look for AWS credentials/region from multiple areas, like the environment variables.\n\nSee [Spring Starter Minimal Example](examples/spring-starter-minimal-example) for a minimal example of configuring in a Spring Boot\napplication with a local ElasticMQ SQS Server.\n\n### Java Core Quick Guide\n\n1. Include the dependency:\n\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n        \u003cartifactId\u003ejava-dynamic-sqs-listener-core\u003c/artifactId\u003e\n        \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n    ```\n\n    or\n\n    ```kotlin\n    dependencies {\n        implementation(\"com.jashmore:java-dynamic-sqs-listener-core:${sqs.listener.version}\")\n    }\n    ```\n\n1. Create a [MessageListenerContainer](api/src/main/java/com/jashmore/sqs/container/MessageListenerContainer.java) for a specific queue.\n\n    ```java\n    public class MyClass {\n\n        public static void main(String[] args) throws InterruptedException {\n            final SqsAsyncClient sqsAsyncClient = SqsAsyncClient.create(); // or your own custom client\n            final QueueProperties queueProperties = QueueProperties.builder().queueUrl(\"${insert.queue.url.here}\").build();\n            final MessageListenerContainer container = new BatchingMessageListenerContainer(\n                \"listener-identifier\",\n                queueProperties,\n                sqsAsyncClient,\n                () -\u003e\n                    new LambdaMessageProcessor(\n                        sqsAsyncClient,\n                        queueProperties,\n                        message -\u003e {\n                            // process message here\n\n                        }\n                    ),\n                ImmutableBatchingMessageListenerContainerProperties\n                    .builder()\n                    .concurrencyLevel(10)\n                    .batchSize(5)\n                    .batchingPeriod(Duration.ofSeconds(20))\n                    .build()\n            );\n            container.start();\n            Runtime.getRuntime().addShutdownHook(new Thread(container::stop));\n            Thread.currentThread().join();\n        }\n    }\n\n    ```\n\nSee the [Core Example](examples/core-example) for a more complicated example that uses a local ElasticMQ server and dynamically changes the concurrency\nof message processing while the app is running.\n\n### Kotlin Quick Guide\n\n1. Include the dependency:\n\n    ```xml\n    \u003cdependencies\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n            \u003cartifactId\u003ejava-dynamic-sqs-listener-core\u003c/artifactId\u003e\n            \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n        \u003c/dependency\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n            \u003cartifactId\u003ecore-kotlin-dsl\u003c/artifactId\u003e\n            \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n    ```\n\n    or\n\n    ```kotlin\n    dependencies {\n        implementation(\"com.jashmore:java-dynamic-sqs-listener-core:${sqs.listener.version}\")\n        implementation(\"com.jashmore:core-kotlin-dsl:${sqs.listener.version}\")\n    }\n    ```\n\n1. Create a [MessageListenerContainer](api/src/main/java/com/jashmore/sqs/container/MessageListenerContainer.java) for a specific queue.\n\n    ```kotlin\n    fun main() {\n        val sqsAsyncClient = SqsAsyncClient.create()\n\n        val container = batchingMessageListener(\"identifier\", sqsAsyncClient, \"url\") {\n            concurrencyLevel = { 10 }\n            batchSize = { 5 }\n            batchingPeriod =  { Duration.ofSeconds(20) }\n\n            processor = lambdaProcessor {\n                method { message -\u003e\n                    log.info(\"Message: {}\", message.body())\n                }\n            }\n        }\n\n        container.start()\n        Runtime.getRuntime().addShutdownHook(\n            Thread {\n                container.stop()\n            }\n        )\n        Thread.currentThread().join()\n    }\n    ```\n\nSee the [Core Kotlin Example](examples/core-kotlin-example) for a full example running a Kotlin App that listens to a local ElasticMQ SQS Server.\n\n### Ktor Quick Guide\n\n1. Include the dependency:\n\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n        \u003cartifactId\u003ejava-dynamic-sqs-listener-ktor-core\u003c/artifactId\u003e\n        \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n    ```\n\n    or\n\n    ```kotlin\n    dependencies {\n        implementation(\"com.jashmore:java-dynamic-sqs-listener-ktor-core:${sqs.listener.version}\")\n    }\n    ```\n\n1. Add a message listener to the server\n\n    ```kotlin\n    fun main() {\n        val sqsAsyncClient = SqsAsyncClient.create() // replace with however you want to configure this client\n        val queueUrl = \"replaceWithQueueUrl\"\n        val server = embeddedServer(Netty, 8080) {\n             batchingMessageListener(\"listener-identifier\", sqsAsyncClient, queueUrl) {\n                  concurrencyLevel = { 10 }\n                  batchSize = { 5 }\n                  batchingPeriod =  { Duration.ofSeconds(20) }\n\n                  processor = lambdaProcessor {\n                      method { message -\u003e\n                          log.info(\"Message: {}\", message.body())\n                      }\n                  }\n             }\n        }\n        server.start()\n        Runtime.getRuntime().addShutdownHook(\n            Thread {\n                server.stop(1, 30_000)\n            }\n        )\n        Thread.currentThread().join()\n    }\n    ```\n\nSee the [Ktor Core Example](examples/ktor-example) for a full example running a Ktor framework that listens to a local ElasticMQ SQS Server.\n\n### Micronaut Quick Guide\n\n1.  Include the Micronaut core dependency with Maven `\u003cdependencies\u003e`:\n\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n        \u003cartifactId\u003ejava-dynamic-sqs-listener-micronaut-core\u003c/artifactId\u003e\n        \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n    ```\n\n    Or with Gradle:\n\n    ```kotlin\n    dependencies {\n        implementation(\"com.jashmore:java-dynamic-sqs-listener-micronaut-core:${sqs.listener.version}\")\n    }\n    ```\n\n1.  Also, include the Micronaut annotation processor with Maven:\n\n    ```xml\n    \u003cpluginManagement\u003e\n        \u003cplugins\u003e\n            \u003cplugin\u003e\n                \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n                \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n                \u003cversion\u003e${maven.compiler.version}\u003c/version\u003e\n                \u003cconfiguration\u003e\n                    \u003cannotationProcessorPaths\u003e\n                        \u003cannotationProcessorPath\u003e\n                            \u003cgroupId\u003ecom.jashmore\u003c/groupId\u003e\n                            \u003cartifactId\u003ejava-dynamic-sqs-listener-micronaut-inject-java\u003c/artifactId\u003e\n                            \u003cversion\u003e${sqs.listener.version}\u003c/version\u003e\n                        \u003c/annotationProcessorPath\u003e\n                    \u003c/annotationProcessorPaths\u003e\n                \u003c/configuration\u003e\n            \u003c/plugin\u003e\n        \u003c/plugins\u003e\n    \u003c/pluginManagement\u003e\n    ```\n\n    Or with Gradle:\n\n    ```kotlin\n    dependencies {\n        annotationProcessor(\"com.jashmore:java-dynamic-sqs-listener-micronaut-inject-java:${sqs.listener.version}\")\n    }\n    ```\n\n    Micronaut will use this at compile time to transform usages of core listener annotations to enable\n    method processors to register annotated methods as message listeners.\n\n1.  In one of your beans, attach a\n    [@QueueListener](./annotations/src/main/java/com/jashmore/sqs/annotations/core/basic/QueueListener.java) or other supported annotation\n    to a method indicating that it should process messages from a queue.\n\n    ```java\n    @Singleton\n    public class MyMessageListener {\n\n        // The queue here can point to your SQS server, e.g. a\n        // local SQS server or one on AWS\n        @QueueListener(\"${insert.queue.url.here}\")\n        public void processMessage(@Payload final String payload) {\n            // process the message payload here\n        }\n    }\n\n    ```\n\n    This will use any configured `SqsAsyncClient` in the application context for connecting to the queue, otherwise a default\n    will be provided that will look for AWS credentials/region from multiple areas, like the environment variables.\n\n## Core Infrastructure\n\nThis library has been divided into isolated components each with distinct responsibilities. The following is a diagram describing a simple flow of a\nsingle SQS message flowing through each of the components to eventually be executed by some code.\n\n![Core Framework Architecture Diagram](./doc/resources/architecture_diagram.png \"Core Framework Architecture Diagram\")\n\nDetails about each component is:\n\n-   The [MessageRetriever](./api/src/main/java/com/jashmore/sqs/retriever/MessageRetriever.java) handles\n    obtaining messages from the SQS queue. This can optimise the retrieval of messages by batching requests for messages or prefetching messages before\n    they are needed.\n-   The [MessageProcessor](./api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java) controls\n    the processing of a message from the queue by delegating it to the corresponding Java method that handles the message.\n-   The [ArgumentResolverService](./api/src/main/java/com/jashmore/sqs/argument/ArgumentResolverService.java) is used by the\n    [MessageProcessor](./api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java) to populate the\n    arguments of the message listener method. For example, a parameter with the\n    [@Payload](./core/src/main/java/com/jashmore/sqs/argument/payload/Payload.java) annotation will be resolved with the\n    body of the message cast to that type (e.g. a POJO).\n-   The [MessageBroker](./api/src/main/java/com/jashmore/sqs/broker/MessageBroker.java) is the main container that controls the whole flow\n    of messages from the [MessageRetriever](./api/src/main/java/com/jashmore/sqs/retriever/MessageRetriever.java) to the\n    [MessageProcessor](./api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java). It can determine when more messages\n    are to be processed and the rate of concurrency for processing messages.\n-   The [MessageResolver](./api/src/main/java/com/jashmore/sqs/resolver/MessageResolver.java) is used after successful processing of the message and its\n    responsibility is to remove the message from the SQS queue, so it is not processed again if there is a re-drive policy.\n\nFor more information about the core implementations provided by this library, see the [Core Implementations Overview](./doc/core-implementations-overview.md).\n\n## Dependencies\n\nThe framework relies on the following dependencies and therefore it is recommended to upgrade the applications dependencies to a point somewhere near these\nfor compatibility.\n\n-   [Core Framework](./core)\n    -   JDK 17 or higher\n    -   [AWS SQS SDK](https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/welcome.html)\n    -   [Jackson Databind](https://github.com/FasterXML/jackson-databind)\n    -   [SLF4J API](https://github.com/qos-ch/slf4j)\n\nThe following require all the core dependencies above.\n\n-   [Spring Framework](./spring)\n    -   [Spring Boot](https://github.com/spring-projects/spring-boot)\n-   [Ktor Framework](./ktor)\n    -   [Kotlin](https://github.com/JetBrains/kotlin)\n    -   [Ktor](https://github.com/ktorio/ktor)\n-   [Micronaut Framework](./micronaut)\n    -   [Micronaut](https://github.com/micronaut-projects/micronaut-core)\n\nSee the [gradle.properties](gradle.properties) for the specific versions of these dependencies.\n\n## How to Guides\n\nMore in-depth guides on how configure this library:\n\n1. [How to Connect to an AWS SQS Queue](doc/how-to-guides/how-to-connect-to-aws-sqs-queue.md): necessary for actually using this framework in live environments\n1. Core Framework How To Guides\n    1. [How to implement a custom ArgumentResolver:](doc/how-to-guides/core/core-how-to-implement-a-custom-argument-resolver.md) useful for changing resolution\n       of arguments in the message listener\n    1. [How to manually acknowledge message](doc/how-to-guides/core/core-how-to-mark-message-as-successfully-processed.md): useful for when you want to mark the\n       message as successfully processed before the method has finished executing\n    1. [How to add Brave Tracing](doc/how-to-guides/core/core-how-to-add-brave-tracing.md): for including Brave Tracing information to your messages\n    1. [How to implement a custom MessageRetriever](doc/how-to-guides/core/core-how-to-implement-a-custom-message-retrieval.md): useful for changing the logic\n       for obtaining messages from the SQS queue if the core implementations do not provided the required functionality\n    1. [How to extend a message's visibility during processing](doc/how-to-guides/core/core-how-to-extend-message-visibility-during-processing.md): useful for\n       extending the visibility of a message in the case of long processing so it does not get put back on the queue while processing\n    1. [How to create a MessageProcessingDecorator](doc/how-to-guides/core/core-how-to-create-a-message-processing-decorator.md): guide for writing your own\n       decorator to wrap a message listener's processing of a message\n    1. [How to use the Core Kotlin DSL](doc/how-to-guides/core/core-how-to-use-kotlin-dsl.md): guide for using the core library easier using a Kotlin\n       DSL for constructing message listeners\n1. Spring How To Guides\n    1. [How to add a custom ArgumentResolver to a Spring application](doc/how-to-guides/spring/spring-how-to-add-custom-argument-resolver.md): useful for\n       integrating custom argument resolution code to be included in a Spring Application. See [How to implement a custom ArgumentResolver](doc/how-to-guides/core/core-how-to-implement-a-custom-argument-resolver.md)\n       for how build a new ArgumentResolver from scratch.\n    1. [How to add Brave Tracing](doc/how-to-guides/spring/spring-how-to-add-brave-tracing.md): for including Brave Tracing information to your messages\n    1. [How to add custom MessageProcessingDecorators](doc/how-to-guides/spring/spring-how-to-add-custom-message-processing-decorators.md): guide on how\n       to autowire custom `MessageProcessingDecorators` into your Spring Queue Listeners.FifoMessageListenerContainerDslBuilde\n    1. [How to change Message Visibility](doc/how-to-guides/spring/spring-how-to-change-message-visibility.md): override the AWS SQS message visibility to a\n       custom value\n    1. [How to extend Message Visibility](doc/how-to-guides/spring/spring-how-to-extend-message-visibility-during-processing.md): manually or automatically\n       extend a message's visibility during processing.\n    1. [How to have dynamic properties](doc/how-to-guides/spring/spring-how-to-have-listener-dynamic-properties.md): guide for overriding the listener\n       annotations to use custom logic.\n    1. [How to customise argument resolution](doc/how-to-guides/spring/spring-how-to-customise-argument-resolution.md): guide for overriding the entire\n       argument resolution logic\n    1. [How to add your own queue listener](doc/how-to-guides/spring/spring-how-to-add-own-queue-listener.md): useful for defining your own annotation for the\n       queue listening without the verbosity of a custom queue listener\n    1. [How to write Spring Integration Tests](doc/how-to-guides/spring/spring-how-to-write-integration-tests.md): you actually want to test what you are\n       writing right?\n    1. [How to prevent listeners from starting on startup](doc/how-to-guides/spring/spring-how-to-prevent-containers-starting-on-startup.md): guide for\n       preventing containers from starting when the Spring application has started up.\n    1. [How to Start/Stop Queue Listeners](doc/how-to-guides/spring/spring-how-to-start-stop-message-listener-containers.md): guide for starting and stopping the\n       processing of messages for specific queue listeners\n    1. [How to connect to multiple AWS Accounts](doc/how-to-guides/spring/spring-how-to-connect-to-multiple-aws-accounts.md): guide for listening to queues\n       across multiple AWS Accounts\n    1. [How to version message payload schemas](doc/how-to-guides/spring/spring-how-to-version-payload-schemas-using-spring-cloud-schema-registry.md): guide\n       for versioning payloads using Avro and the Spring Cloud Schema Registry.\n1. Ktor How to Guides\n    1. [How to Register Message Listeners](doc/how-to-guides/ktor/ktor-how-to-register-message-listeners.md): guide for include message listeners into a\n       Ktor application.\n\n## Common Use Cases/Explanations\n\n### Core Processor - How to de-serialise a JSON Payload\n\nWhen you are using the reflection based [CoreMessageProcessor](core/src/main/java/com/jashmore/sqs/processor/CoreMessageProcessor.java) (which is the default for\nSpring Boot applications), the payload of the message is de-serialised by default using [Jackson](https://github.com/FasterXML/jackson-databind) and\ntherefore any Jackson compatible POJO class can be used with the [@Payload](core/src/main/java/com/jashmore/sqs/argument/payload/Payload.java) annotation.\n\n```java\n@Service\npublic class MyMessageListener {\n\n    @QueueListener(value = \"${insert.queue.url.here}\")\n    public void processMessage(@Payload final MyPojo payload) {\n        // process the message payload here\n    }\n\n    public static class MyPojo {\n\n        private String name;\n\n        public MyPojo() {\n            this.name = null;\n        }\n\n        public MyPojo(String name) {\n            this.name = null;\n        }\n\n        public void setName(String name) {\n            this.name = name;\n        }\n\n        public String getName() {\n            return name;\n        }\n    }\n}\n\n```\n\n### Micronaut\n\nThe `micronaut-core` library is applied pretty much the same way as `spring-starter`,\nso for Micronaut it will be useful to look through the Spring guides and examples.\n\n### Spring - Adding a custom argument resolver\n\nThere are some core [ArgumentResolvers](./api/src/main/java/com/jashmore/sqs/argument/ArgumentResolver.java) provided in the\napplication but custom ones can be defined if they don't cover your use case. As an example, the following is how we can populate the message listener\nargument with the payload in uppercase.\n\n1.  We will use an annotation on the field to indicate how the message should be resolved.\n\n    ```java\n    @Retention(value = RUNTIME)\n    @Target(ElementType.PARAMETER)\n    public @interface UppercasePayload {\n    }\n\n    ```\n\n1.  Implement the [ArgumentResolver](./api/src/main/java/com/jashmore/sqs/argument/ArgumentResolver.java) interface where it will\n    do the logic for converting the message payload to uppercase.\n\n    ```java\n    public class UppercasePayloadArgumentResolver implements ArgumentResolver\u003cString\u003e {\n\n        @Override\n        public boolean canResolveParameter(MethodParameter methodParameter) {\n            return (\n                methodParameter.getParameter().getType().isAssignableFrom(String.class) \u0026\u0026\n                AnnotationUtils.findParameterAnnotation(methodParameter, UppercasePayload.class).isPresent()\n            );\n        }\n\n        @Override\n        public String resolveArgumentForParameter(QueueProperties queueProperties, Parameter parameter, Message message)\n            throws ArgumentResolutionException {\n            return message.body().toUppercase();\n        }\n    }\n\n    ```\n\n    You may be curious why we use a custom `AnnotationUtils.findParameterAnnotation` function instead of getting the annotation directly from the parameter.\n    The reason for this is due to potential proxying of beans in the application, such as by applying Aspects around your code via CGLIB. As libraries, like\n    CGLIB, won't copy the annotations to the proxied classes the resolver needs to look through the class hierarchy to find the original class to get the\n    annotations. For more information about this, take a look at the JavaDoc provided in\n    [AnnotationUtils](./util/annotation-utils/src/main/java/com/jashmore/sqs/util/annotation/AnnotationUtils.java). You can also see an example of\n    testing this problem in\n    [PayloadArgumentResolver_ProxyClassTest.java](./core/src/test/java/com/jashmore/sqs/argument/payload/PayloadArgumentResolver_ProxyClassTest.java).\n\n    Also, as this library is not Spring specific, the Spring Annotation classes cannot be used.\n\n1.  Include the custom [ArgumentResolver](./api/src/main/java/com/jashmore/sqs/argument/ArgumentResolver.java) in the application\n    context for automatic injection into the\n    [ArgumentResolverService](./api/src/main/java/com/jashmore/sqs/argument/ArgumentResolverService.java).\n\n    ```java\n    @Configuration\n    public class MyCustomConfiguration {\n\n        @Bean\n        public UppercasePayloadArgumentResolver uppercasePayloadArgumentResolver() {\n            return new UppercasePayloadArgumentResolver();\n        }\n    }\n\n    ```\n\n1.  Use the new annotation in your message listener\n\n    ```java\n    @Component\n    public class MyService {\n\n        @QueueListener(\"${insert.queue.url.here}\") // The queue here can point to your SQS server, e.g. a local SQS server or one on AWS\n        public void processMessage(@UppercasePayload final String uppercasePayload) {\n            // process the message payload here\n        }\n    }\n\n    ```\n\nFor a more extensive guide for doing this, take a look at\n[Spring - How to add a custom Argument Resolver](doc/how-to-guides/spring/spring-how-to-add-custom-argument-resolver.md). If you are using the core\nor Ktor library, you can look at\nthe [Core - How to Implement a Custom Argument Resolver](doc/how-to-guides/core/core-how-to-implement-a-custom-argument-resolver.md) for a guide on creating\na new argument resolver.\n\n### Increasing the concurrency limit\n\nThere is no limit to the number of messages that can be processed in the application and therefore you can process as many messages to the limit\nof the threads that the application can handle. Therefore, if you are fine spinning up as many threads as concurrent messages, you can increase\nthe concurrency to as high of a value as you wish.\n\n#### Core\n\n```java\npublic class SomeClass {\n\n    public MessageListenerContainer container() {\n        return new BatchingMessageListenerContainer(\n            // other configuration\n            ImmutableBatchingMessageListenerContainerProperties\n                .builder()\n                .concurrencyLevel(100)\n                .batchSize(5)\n                .batchingPeriod(Duration.ofSeconds(20))\n                .build()\n        );\n    }\n}\n\n```\n\n#### Spring Boot\n\n```java\n@Service\npublic class MyMessageListener {\n\n    @QueueListener(value = \"${insert.queue.url.here}\", concurrencyLevel = 100)\n    public void processMessage(@Payload final String payload) {\n        // process message here\n    }\n}\n\n```\n\n#### Kotlin DSL/Ktor\n\n```kotlin\ncoreMessageListener(\"identifier\", sqsAsyncClient, \"${insert.queue.url.here}\") {\n    broker = concurrentBroker {\n        concurrencyLevel = { 100 }\n    }\n    // other configuration\n}\n```\n\nor\n\n```kotlin\nbatchingMessageListener(\"identifier\", sqsAsyncClient, \"${insert.queue.url.here}\") {\n    concurrencyLevel = { 100 }\n    // other configuration\n}\n```\n\n### How to Mark the message as successfully processed\n\nWhen the method executing the message finishes without throwing an exception, the\n[MessageProcessor](./api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java) will acknowledge the message\nas a success, therefore removing it from the queue. When the method throws an exception, the message will not be acknowledged and if there is a re-drive\npolicy the queue will perform another attempt of processing the message.\n\n```java\npublic class MyMessageListener {\n\n    @QueueListener(value = \"queue-name\")\n    public void processMessage(@Payload final String payload) {\n        // if this does not throw an exception it will be considered successfully processed\n    }\n}\n\n```\n\nor\n\n```kotlin\nlambdaProcessor {\n    method { message -\u003e\n        // if this does not throw an exception it will be considered successfully processed\n    }\n}\n```\n\nIf the method contains an\n[Acknowledge](./api/src/main/java/com/jashmore/sqs/processor/argument/Acknowledge.java) argument it is now up to the method\nto manually acknowledge the message as a success. The [MessageProcessor](./api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java)\nhas handed off control to the message listener and will not acknowledge the message automatically when the method executes without throwing an exception.\n\n```java\npublic class MyMessageListener {\n\n    @QueueListener(value = \"${insert.queue.url.here}\", concurrencyLevel = 10, maxPeriodBetweenBatchesInMs = 2000)\n    public void processMessage(@Payload final String payload, final Acknowledge acknowledge) {\n        if (someCondition()) {\n            CompletableFuture\u003c?\u003e future = acknowledge.acknowledgeSuccessful();\n            future.get();\n        }\n    }\n}\n\n```\n\nor\n\n```kotlin\nlambdaProcessor {\n    method { message, acknowledge -\u003e\n        if (someCondition()) {\n            acknowledge.acknowledgeSuccessful().get()\n        }\n    }\n}\n```\n\n### Setting up a queue listener that batches requests for messages\n\nThe [Spring Cloud AWS Messaging](https://github.com/spring-cloud/spring-cloud-aws/tree/master/spring-cloud-aws-messaging) `@SqsListener` works by requesting\na set of messages from the SQS and when they are done it will request some more. There is one disadvantage with this approach in that if 9/10 of the messages\nfinish in 10 milliseconds but one takes 10 seconds no other messages will be picked up until that last message is complete. The\n[@QueueListener](./annotations/src/main/java/com/jashmore/sqs/annotations/core/basic/QueueListener.java)\nprovides the same basic functionality, but it also provides a timeout where it will eventually request for more messages when there are threads that are\nready for another message.\n\n#### Core/Spring Boot/Micronaut\n\n```java\npublic class MyMessageListener {\n\n    @QueueListener(value = \"${insert.queue.url.here}\", concurrencyLevel = 10, maxPeriodBetweenBatchesInMs = 2000)\n    public void processMessage(@Payload final String payload) {\n        // process the message payload here\n    }\n}\n\n```\n\n#### Kotlin/Ktor\n\n```kotlin\nbatchingMessageListener(\"listener-identifier\", sqsAsyncClient, \"${insert.queue.url.here}\") {\n    concurrencyLevel = { 10 }\n    batchSize = { 10 }\n    batchingPeriod = { Duration.ofSeconds(2) }\n    processor = lambdaProcessor {\n        method { message -\u003e\n          // process the message payload here\n        }\n    }\n}\n```\n\nIn this example above we have set it to process 10 messages at once and when there are threads wanting more messages it will wait for a maximum of 2 seconds\nbefore requesting messages for threads waiting for another message.\n\n### Setting up a queue listener that prefetches messages\n\nWhen the amount of messages for a service is extremely high, prefetching messages may be a way to optimise the throughput of the application. In this\nexample, if the amount of prefetched messages is below the desired amount of prefetched messages it will try to get as many messages as possible up\nto the maximum specified.\n\n_Note: because of the limit of the number of messages that can be obtained from SQS at once (10), having the maxPrefetchedMessages more than\n10 above the desiredMinPrefetchedMessages will not provide much value as once it has prefetched more than the desired prefetched messages it will\nnot prefetch anymore._\n\n#### Spring Boot\n\nThe [@PrefetchingQueueListener](./annotations/src/main/java/com/jashmore/sqs/annotations/core/prefetch/PrefetchingQueueListener.java)\nannotation can be used to prefetch messages in a background thread while processing the existing messages. The usage is something like this:\n\n```java\n@Service\npublic class MyMessageListener {\n\n    @PrefetchingQueueListener(\n        value = \"${insert.queue.url.here}\",\n        concurrencyLevel = 10,\n        desiredMinPrefetchedMessages = 5,\n        maxPrefetchedMessages = 10\n    )\n    public void processMessage(@Payload final String payload) {\n        // process the message payload here\n    }\n}\n\n```\n\n#### Kotlin DSL/Ktor\n\n```kotlin\nprefetchingMessageListener(\"identifier\", sqsAsyncClient, \"${insert.queue.url.here}\") {\n    concurrencyLevel = { 10 }\n    desiredPrefetchedMessages = 5\n    maxPrefetchedMessages = 10\n\n    processor = lambdaProcessor {\n        methodWithVisibilityExtender { message, _ -\u003e\n            // process the message payload here\n        }\n    }\n}\n```\n\n### Listening to a FIFO SQS Queue\n\nFIFO SQS Queues can be used when the order of the SQS messages are important. The FIFO message listener guarantees messages\nin the same message group run in the order they are generated and two messages in the same group executed concurrently. For more information about\nthe configuration options for the message listener take a look at the\n[FifoMessageListenerContainerProperties](core/src/main/java/com/jashmore/sqs/container/fifo/FifoMessageListenerContainerProperties.java) or the specific\nannotation or DSL builder for the framework implementation.\n\n### Java\n\n```java\npublic class Main {\n\n    public static void main(String[] args) throws InterruptedException {\n        final SqsAsyncClient sqsAsyncClient = SqsAsyncClient.create(); // or your own custom client\n        final QueueProperties queueProperties = QueueProperties.builder().queueUrl(\"${insert.queue.url.here}\").build();\n        final MessageListenerContainer container = new FifoMessageListenerContainer(\n            queueProperties,\n            sqsAsyncClient,\n            () -\u003e\n                new LambdaMessageProcessor(\n                    sqsAsyncClient,\n                    queueProperties,\n                    message -\u003e {\n                        // process the message here\n                    }\n                ),\n            ImmutableFifoMessageListenerContainerProperties.builder().identifier(\"listener-identifier\").concurrencyLevel(10).build()\n        );\n        container.start();\n        Runtime.getRuntime().addShutdownHook(new Thread(container::stop));\n        Thread.currentThread().join();\n    }\n}\n\n```\n\n### Spring Boot\n\n```java\n@Component\nclass MessageListeners {\n\n    @FifoQueueListener(value = \"${insert.queue.url.here}\", concurrencyLevel = 10)\n    public void fifoListener(@Payload final String body) {\n        // process message here\n    }\n}\n\n```\n\n### Kotlin DSL/Ktor\n\n```kotlin\nfifoMessageListener(\"identifier\", sqsAsyncClient, \"${insert.queue.url.here}\") {\n    concurrencyLevel = { 10 }\n\n    processor = lambdaProcessor {\n        method { message -\u003e\n            // process the message payload here\n        }\n    }\n}\n```\n\n## Examples\n\nSee [examples](./examples) for all the available examples.\n\n### Testing locally an example Spring Boot app with the Spring Starter\n\nThe easiest way to see the framework working is to run one of the examples locally. These use an in memory [ElasticMQ](https://github.com/adamw/elasticmq)\nSQS Server to simplify getting started. For example, to run a sample Spring Application you can use\nthe [Spring Starter Example](examples/spring-starter-example/src/main/java/com/jashmore/sqs/examples).\n\n1. Build the framework\n\n```bash\ngradle build -x test -x integrationTest\n```\n\n1. Run the Spring Starer Example Spring Boot app\n\n```bash\n(cd examples/spring-starter-example \u0026\u0026 gradle bootRun)\n```\n\n### Testing locally a dynamic concurrency example\n\nThis shows an example of running the SQS Listener in a Java application that will dynamically change the concurrency level while it is executing.\n\nThis examples works by having a thread constantly placing new messages while the SQS Listener will randomly change\nthe rate of concurrency every 10 seconds.\n\n1. Build the framework\n\n```bash\ngradle build -x test -x integrationTest\n```\n\n1. Run the Spring Starer Example Spring Boot app\n\n```bash\n(cd examples/core-example \u0026\u0026 gradle runApp)\n```\n\n## Bugs and Feedback\n\nFor bugs, questions and discussions please use [Github Issues](https://github.com/JaidenAshmore/java-dynamic-sqs-listener/issues).\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.\n\n## License\n\n```text\nMIT License\n\nCopyright (c) 2018 Jaiden Ashmore\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaidenashmore%2Fjava-dynamic-sqs-listener","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaidenashmore%2Fjava-dynamic-sqs-listener","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaidenashmore%2Fjava-dynamic-sqs-listener/lists"}