{"id":39030798,"url":"https://github.com/joel-jeremy/emissary","last_synced_at":"2026-01-17T17:38:49.079Z","repository":{"id":144584438,"uuid":"532778554","full_name":"joel-jeremy/emissary","owner":"joel-jeremy","description":"A simple yet 🗲FAST🗲 library to dispatch requests and events to corresponding handlers 🚀","archived":false,"fork":false,"pushed_at":"2026-01-14T21:47:10.000Z","size":705,"stargazers_count":64,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-14T22:57:14.256Z","etag":null,"topics":["dispatcher","event","eventbus","java","jvm","messaging","publisher","requests"],"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/joel-jeremy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"joel-jeremy","buy_me_a_coffee":"joeljeremy","custom":["https://www.paypal.me/joeljeremymarquez"]}},"created_at":"2022-09-05T06:53:05.000Z","updated_at":"2026-01-14T21:25:32.000Z","dependencies_parsed_at":"2024-01-03T01:21:26.221Z","dependency_job_id":"7c4fbae6-918a-4202-ba32-4f964f465243","html_url":"https://github.com/joel-jeremy/emissary","commit_stats":null,"previous_names":["joel-jeremy/emissary"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/joel-jeremy/emissary","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joel-jeremy%2Femissary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joel-jeremy%2Femissary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joel-jeremy%2Femissary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joel-jeremy%2Femissary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joel-jeremy","download_url":"https://codeload.github.com/joel-jeremy/emissary/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joel-jeremy%2Femissary/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28513817,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["dispatcher","event","eventbus","java","jvm","messaging","publisher","requests"],"created_at":"2026-01-17T17:38:47.412Z","updated_at":"2026-01-17T17:38:49.071Z","avatar_url":"https://github.com/joel-jeremy.png","language":"Java","funding_links":["https://github.com/sponsors/joel-jeremy","https://buymeacoffee.com/joeljeremy","https://www.paypal.me/joeljeremymarquez"],"categories":["Projects","进程间通信"],"sub_categories":["Messaging","Spring Cloud框架"],"readme":"# Emissary (formerly Deezpatch)\n\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/joel-jeremy/emissary/blob/main/LICENSE)\n[![Gradle Build](https://github.com/joel-jeremy/emissary/actions/workflows/gradle-build.yaml/badge.svg)](https://github.com/joel-jeremy/emissary/actions/workflows/gradle-build.yaml)\n[![Code QL](https://github.com/joel-jeremy/emissary/actions/workflows/codeql.yaml/badge.svg)](https://github.com/joel-jeremy/emissary/actions/workflows/codeql.yaml)\n[![Sonatype Central](https://maven-badges.sml.io/sonatype-central/io.github.joel-jeremy.emissary/emissary-core/badge.svg)](https://maven-badges.sml.io/sonatype-central/io.github.joel-jeremy.emissary/emissary-core)\n[![codecov](https://codecov.io/gh/joel-jeremy/emissary/graph/badge.svg?token=1KVQMOXYHT)](https://codecov.io/gh/joel-jeremy/emissary)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.emissary\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=ncloc)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=io.github.joel-jeremy.externalized-properties\u0026metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n[![Discord](https://img.shields.io/discord/1025648239162175578.svg?logo=discord\u0026logoColor=white\u0026logoWidth=20\u0026labelColor=7289DA\u0026label=Discord\u0026color=17cf48)](https://discord.gg/bAfgXRVx3T)\n\u003c!-- Commenting out until issue gets fixed: https://github.com/snyk/cli/issues/668 --\u003e\n\u003c!-- [![Known Vulnerabilities](https://snyk.io/test/github/joel-jeremy/emissary/badge.svg)](https://snyk.io/test/github/joel-jeremy/emissary) --\u003e\n\nA simple yet 🗲FAST🗲 library to dispatch requests and events to corresponding handlers 🚀\n\nThe library aims to take advantage of the intuitiveness of using the annotations for handlers (e.g. `@RequestHandler`/`@EventHandler`) without the drawbacks of reflection.\n\nThe library aims to help build applications which apply the [Command Query Responsibility Segregation](https://martinfowler.com/bliki/CQRS.html) (CQRS) pattern.  \n\n## Like the project?\n\nPlease consider giving the repository a ⭐. It means a lot! Thank you :)\n\n## Get Emissary\n\n\u003e [!IMPORTANT]  \n\u003e Up until v1.1.0, the core library is published under the old `deezpatch-core` name. This has been renamed to `emissary-core` starting from v2.0.0 onwards.\n\n### Gradle\n\n```groovy\nimplementation \"io.github.joel-jeremy.emissary:emissary-core:${version}\"\n```\n\n### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.joel-jeremy.emissary\u003c/groupId\u003e\n    \u003cartifactId\u003eemissary-core\u003c/artifactId\u003e\n    \u003cversion\u003e${version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Java 9 Module Names\n\n\u003e [!IMPORTANT]  \n\u003e Up until v1.1.0, the core library has the module name `io.github.joeljeremy.deezpatch.core`. This has been renamed to `io.github.joeljeremy.emissary.core` starting from v2.0.0 onwards.\n\nEmissary jars are published with Automatic-Module-Name manifest attribute:\n\n- Core - `io.github.joeljeremy.emissary.core`\n\nModule authors can use above module names in their module-info.java:\n\n```java\nmodule foo.bar {\n    requires io.github.joeljeremy.emissary.core;\n}\n```\n\n## Performance\n\nWhat differentiates Emissary from other messaging/dispatch libraries? The library takes advantage of the benefits provided by [java.lang.invoke.LambdaMetafactory](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html) to avoid the cost of invoking methods reflectively. This results in performance close to directly invoking the request handler and event handler methods!\n\n### ~ 1000% more throughput compared to other similar libraries (Spring Events, Pipelinr, etc)\n### ~ 90% less time compared to other similar libraries (Spring Events, Pipelinr, etc)\n\n### [Java 11 Benchmarks](https://jmh.morethan.io/?source=https://raw.githubusercontent.com/joel-jeremy/emissary/main/emissary-core/src/jmh/java/io/github/joeljeremy/emissary/core/benchmarks/results-java11.json)\n\n### [Java 17 Benchmarks](https://jmh.morethan.io/?source=https://raw.githubusercontent.com/joel-jeremy/emissary/main/emissary-core/src/jmh/java/io/github/joeljeremy/emissary/core/benchmarks/results-java17.json)\n\n## Requests\n\nRequests are messages that either:\n\n1. Initiate a state change/mutation\n    - Commands in [CQRS](https://martinfowler.com/bliki/CQRS.html)\n2. Retrieve/query data\n    - Queries in [CQRS](https://martinfowler.com/bliki/CQRS.html)\n\n```java\npublic class GreetCommand implements Request\u003cVoid\u003e {\n    private final String name;\n    \n    public GreetRequest(String name) {\n        this.name = name;\n    }\n    \n    public String name() {\n        return name;\n    }\n}\n\npublic class PingQuery implements Request\u003cPong\u003e {}\n```\n\n## Request Handlers\n\nRequests are handled by request handlers. Request handlers can be registered through the use of the [@RequestHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/RequestHandler.java) annotation.\n\nA request must only have a single request handler.\n\n**(`@RequestHandler`s fully support methods with `void` return types! No need to set method return type to `Void` and return `null` for no reason.)**\n\n```java\npublic class GreetCommandHandler {\n    @RequestHandler\n    public void handle(GreetCommand command) {\n        sayHi(command.name());\n    }\n}\n\npublic class PingQueryHandler {\n    @RequestHandler\n    public Pong handle(PingQuery query) {\n        return new Pong(\"Here's your pong!\");\n    }\n}\n```\n\n## Request Dispatcher\n\nRequests are dispatched to a single request handler and this can be done through a dispatcher.\n\n```java\npublic static void main(String[] args) {\n    // Use Spring's application context as InstanceProvider in this example\n    // but any other DI framework can be used e.g. Guice, Dagger, etc.\n    ApplicationContext applicationContext = springApplicationContext();\n\n    // Emissary implements the Dispatcher interface.\n    Dispatcher dispatcher = Emissary.builder()\n        .instanceProvider(applicationContext::getBean)\n        .requests(config -\u003e config.handlers(\n            GreetCommandHandler.java,\n            PingQueryHandler.java\n        ))\n        .build();\n\n    // Send command!\n    dispatcher.send(new GreetCommand(\"Deez\"));\n\n    // Send query!\n    Optional\u003cPong\u003e pong = dispatcher.send(new PingQuery());\n}\n```\n\n## Events\n\nEvents are messages that indicate that something has occurred in the system.\n\n```java\npublic class GreetedEvent implements Event {\n    private final String greeting;\n\n    public GreetedEvent(String greeting) {\n        this.greeting = greeting;\n    }\n\n    public String greeting() {\n        return greeting;\n    }\n}\n```\n\n## Event Handlers\n\nEvents are handled by event handlers. Event handlers can be registered through the use of the [@EventHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/EventHandler.java) annotation.\n\nAn event can have zero or more event handlers.\n\n```java\npublic class GreetedEventHandler {\n    @EventHandler\n    public void sayHello(GreetedEvent event) {\n        // Well, hello!\n    }\n\n    @EventHandler\n    public void sayKumusta(GreetedEvent event) {\n        // Well, kumusta?\n    }\n\n    @EventHandler\n    public void sayGotEm(GreetedEvent event) {\n        // Got 'em! \n    }\n}\n```\n\n## Event Publisher\n\nEvents are dispatched to zero or more event handlers and this can be done through a publisher.\n\n```java\npublic static void main(String[] args) {\n    // Use Spring's application context as InstanceProvider in this example\n    // but any other DI framework can be used e.g. Guice, Dagger, etc.\n    ApplicationContext applicationContext = springApplicationContext();\n\n    // Emissary implements the Publisher interface.\n    Publisher publisher = Emissary.builder()\n        .instanceProvider(applicationContext::getBean)\n        .events(config -\u003e config.handlers(\n            GreetedEventHandler.java\n        ))\n        .build();\n\n    // Publish event!\n    publisher.publish(new GreetedEvent(\"Hi from Deez!\"));\n}\n```\n\n## Easy Integration with Dependency Injection (DI) Frameworks\n\nThe library provides an [InstanceProvider](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/InstanceProvider.java) interface as an extension point to let users customize how request/event handler instances should be instantiated. This can be as simple as `new`-ing up request/event handlers or getting instances from a DI framework such as Spring's `ApplicationContext`, Guice's `Injector`, etc.\n\n### Example with No DI framework\n\n```java\n// Application.java\n\npublic static void main(String[] args) {\n  Emissary emissary = Emissary.builder()\n      .instanceProvider(Application::getInstance)\n      .requests(...)\n      .events(...)\n      .build();\n}\n\nprivate static Object getInstance(Class\u003c?\u003e handlerType) {\n  if (MyRequestHandler.class.equals(handlerType)) {\n    return new MyRequestHandler();\n  } else if (MyEventHandler.class.equals(handlerType)) {\n    return new MyEventHandler();\n  }\n\n  throw new IllegalStateException(\"Failed to get instance for \" + handlerType.getName() + \".\");\n}\n```\n\n### Example with Spring's ApplicationContext\n\n```java\npublic static void main(String[] args) {\n  ApplicationContext applicationContext = springApplicationContext();\n  Emissary emissary = Emissary.builder()\n      .instanceProvider(applicationContext::getBean)\n      .requests(...)\n      .events(...)\n      .build();\n}\n```\n\n### Example with Guice's Injector\n\n```java\npublic static void main(String[] args) {\n  Injector injector = guiceInjector();\n  Emissary emissary = Emissary.builder()\n      .instanceProvider(injector::getInstance)\n      .requests(...)\n      .events(...)\n      .build();\n}\n```\n\n## Custom Request/Event Handler Annotations\n\nIn cases where a project is built in such a way that bringing in external dependencies is considered a bad practice (e.g. domain layer/package in a Hexagonal (Ports and Adapters) architecture), Emissary provides a way to use custom request/event handler annotations (in addition to the built-in [RequestHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/RequestHandler.java) and [EventHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/EventHandler.java) annotations) to annotate request/event handlers.\n\nThis way, Emissary can still be used without adding the core Emissary library as a dependency of a project's domain layer/package. Instead, it may be used in the outer layers/packages to wire things up.\n\n```java\n// Let's say below classes are declared in a project's core/domain package:\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\npublic @interface AwesomeRequestHandler {}\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\npublic @interface AwesomeEventHandler {}\n\npublic class MyRequestHandler {\n  @AwesomeRequestHandler\n  public void handle(TestRequest request) {\n    // Handle.\n  }\n}\n\npublic class MyEventHandler {\n  @AwesomeEventHandler\n  public void handle(TestEvent event) {\n    // Handle.\n  }\n}\n\n// To wire things up:\n\npublic static void main(String[] args) {\n  // Use Spring's application context as InstanceProvider in this example\n  // but any other DI framework can be used e.g. Guice, Dagger, etc.\n  ApplicationContext applicationContext = springApplicationContext();\n\n  // Register handlers and custom annotations.\n  Emissary emissary = Emissary.builder()\n      .instanceProvider(applicationContext::getBean)\n      .requests(config -\u003e \n          config.handlerAnnotations(AwesomeRequestHandler.class)\n              .handlers(MyRequestHandler.class))\n      .events(config -\u003e \n          config.handlerAnnotations(AwesomeEventHandler.java)\n              .handlers(MyEventHandler.class))\n      .build();\n}\n```\n\n## Custom Invocation Strategies\n\nThe library provides [Emissary.RequestHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/Emissary.java) and [Emissary.EventHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/Emissary.java) interfaces as extension points to let users customize how request/event handler methods are invoked by the Dispatcher and Publisher.\n\nBuilt-in implementations are:\n- [SyncRequestHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/invocationstrategies/SyncRequestHandlerInvocationStrategy.java) (Default)\n- [SyncEventHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/invocationstrategies/SyncEventHandlerInvocationStrategy.java) (Default)\n- [AsyncEventHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/invocationstrategies/AsyncEventHandlerInvocationStrategy.java)\n\nUsers can create a new implementation and override the defaults by:\n```java\n// Register custom invocation strategy.\nEmissary emissary = Emissary.builder()\n    .requests(config -\u003e \n        config.invocationStrategy(\n            new LoggingInvocationStrategy(\n                new RetryOnErrorInvocationStrategy())))\n    .events(config -\u003e \n        config.invocationStrategy(\n            new LoggingInvocationStrategy(\n                new OrderGuaranteedInvocationStrategy())))\n      .build();\n```\n\n---\n\n[![SonarQube Cloud](https://sonarcloud.io/images/project_badges/sonarcloud-light.svg)](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoel-jeremy%2Femissary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoel-jeremy%2Femissary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoel-jeremy%2Femissary/lists"}