Skip to content

Status: Stable Package: dev.vertique.services Artifact: vertique-services Depends on: core, context, correlation, deploy, logging, resilience, security-core, security-runtime API reference: javadoc.io

vertique-services is Vertique’s contract-based execution model. Applications define typed operations as annotated Java interfaces, implement them as ordinary injectable classes, and inject the contract itself wherever an operation must be called. The generated services Dagger module provides a singleton typed client for each eligible contract. Vertique captures registered dispatch context, routes the invocation over the Vert.x event bus, restores that context on receipt, and owns service lifecycle, supervision, authorization, resilience, failure transport, and observability.

Each contract runs on one or more independently configurable ServiceVerticle instances. Every instance has its own Vert.x event loop and registers consumers at the contract’s operation addresses, so instance count scales consumers and concurrent execution. The instances share the single selected implementation object stored in the contract entry; scaling does not create isolated handler state, so implementations must remain safe for concurrent calls. Kafka listeners, cron schedules, delayed jobs, workflows, and outbox relays can resolve and invoke explicitly identified operations through their stable target ids.


Use this module when an application needs:

  • a typed boundary between independently testable application capabilities;
  • asynchronous Future<T> request/reply without exposing event-bus addresses to callers;
  • service-level timeout, retry, circuit-breaker, authorization, or observability policies;
  • isolated service deployment and supervision within one Vert.x application; or
  • a durable service-operation identity for Kafka, cron, delayed jobs, workflows, or outbox delivery.

For a simple function call within one cohesive component, prefer a normal injected Java interface. For communication between processes, use a network or broker integration with an explicit serialization and compatibility contract.


Vertique Services execution model

The Java interface is both the caller’s dependency and the framework contract. Generated Dagger wiring supplies the typed client; service integrations use the same operation metadata through a stable identity. Both paths converge on the dispatch runtime and the managed service verticles.

Annotate an interface with @ServiceContract. Its namespace and value form the logical service identity. Each method is an operation and must return Future<T> or Future<Void>.

@ServiceOperation("id") gives an operation a durable identity. Without it, the Java method name is used for runtime routing and the operation cannot be resolved as a stable target.

Declaration Runtime address Stable target id
namespace integration, service users, operation find services/integration/users/find integration.users.find
no namespace, service users, operation ping without @ServiceOperation services/users/ping none

Changing a namespace, service name, or explicit operation id breaks durable references that store the stable target id.

The recommended server shape implements ServiceHandler<Contract> instead of the contract itself. Handler methods match contract methods by Java method name and may add framework-provided parameters such as SecurityContext. This keeps caller-visible contracts free of server-only context.

Direct implementations (implements Contract) remain supported. Do not register both a direct implementation and a handler for the same contract.

The services annotation processor discovers service implementations and source-root service contracts, then generates GeneratedServicesModule. Its @Provides @IntoSet contributor bindings register compile-time-validated contract metadata and implementation providers. Its @Provides @Singleton typed-client bindings call ServiceClientFactory.create(Contract.class), making each eligible contract directly injectable. Add that generated module and DispatchModule to the application component.

Application code normally injects the contract directly:

@Inject
OrderCoordinator(FulfilmentService fulfilmentService) {
this.fulfilmentService = fulfilmentService;
}

The generated provider delegates to ServiceClientFactory.create(Contract.class). create() selects a generated {Contract}_ServiceClientProxy companion when one is on the classpath — emitted by vertique-codegen-services for every source-root @ServiceContract interface — and falls back to a JDK dynamic proxy otherwise. Either way, the client captures dispatch context, resolves operation metadata, and delegates transport concerns to the framework.

ServiceClientFactory remains available for deliberate dynamic or manual wiring. A contract annotated @NoAutoWire suppresses only its generated typed-client binding, so an application-owned @Provides method can bind it explicitly. @NoAutoWire on an implementation keeps its existing server-registration semantics and does not suppress a valid client binding for the contract.

Request/reply is the default. The returned future completes with the service result or a transported failure.

@OneWay uses fire-and-forget delivery and is restricted to Future<Void>. Successful completion means only that the message was submitted to the event bus. It does not prove that a consumer received or processed it, and server failures cannot reach the caller.

DispatchEnvelope may additionally carry an optional reply address alongside the payload and dispatch metadata. When one is present, the operation’s outcome — success, or the final failure after any ServiceInterceptor recovery — is published to that address instead of being sent back on the originating event-bus message. This is a third delivery mode (“fire-and-report”), distinct from both request/reply and @OneWay.

The reply address is a property of how an operation is dispatched, not of the operation’s own annotations: when present, it is honored regardless of whether the target method is @OneWay.

The jobs infrastructure (cron and delayed-job scheduling) is the framework dispatcher that uses it. Each dispatch sets a reply address before sending, then listens for the outcome independently of the original send — letting a scheduler hand an operation off without holding a live request/reply future open across the operation’s entire execution. The outcome — or a separate execution-timeout timer, if none ever arrives — drives that scheduler’s own completion handling: delayed-job retries with backoff up to its configured attempt limit before dead-lettering; cron records a terminal outcome per fire with no automatic retry, since its own recurrence produces the next execution.

ServiceClientFactory’s typed client never populates a reply address, for either delivery mode it uses:

Client delivery mode Reply address Outcome delivery
@OneWay never set Fire-and-forget; the returned future completes once the message is submitted. No outcome — success or failure — ever reaches the caller.
Request/reply (default) never set Carried back on the originating request via the event bus’s own reply mechanism; the returned future completes with the service result or a transported failure.

Each registered service contract is deployed as one or more ServiceVerticle instances according to services.contracts.<namespace>.<name>.instances. Vert.x deploys every configured instance on its own event loop; the instances register consumers for the same operation addresses and share the work. Operations run on the event loop by default; blocking handlers must use worker deployment (worker: true) or move blocking work behind an appropriate asynchronous boundary.

DispatchModule contributes service startup and shutdown steps. Applications using the standard Vertique lifecycle do not call a deployment manager directly.


package com.example.users;
import dev.vertique.resilience.annotation.Retry;
import dev.vertique.resilience.annotation.Timeout;
import dev.vertique.services.OneWay;
import dev.vertique.services.ServiceContract;
import dev.vertique.services.ServiceOperation;
import io.vertx.core.Future;
@ServiceContract(namespace = "accounts", value = "users")
public interface UserService {
@ServiceOperation("display-name")
@Timeout(2_000)
@Retry(maxRetries = 2, delayMs = 50)
Future<String> displayName(String userId);
@OneWay
@ServiceOperation("invalidate-cache")
Future<Void> invalidateCache(String userId);
}

At most one parameter may be a domain payload. A contract may declare dispatch-context values, but server-only values such as SecurityContext are better added to a ServiceHandler method.

package com.example.users;
import dev.vertique.security.SecurityContext;
import dev.vertique.services.ServiceHandler;
import io.vertx.core.Future;
import jakarta.inject.Inject;
public final class UserServiceHandler implements ServiceHandler<UserService> {
@Inject
public UserServiceHandler() {}
public Future<String> displayName(String userId, SecurityContext securityContext) {
return Future.succeededFuture("User " + userId);
}
public Future<Void> invalidateCache(String userId) {
return Future.succeededFuture();
}
}

Handler matching is strict:

  • method names match the Java symbols on the contract, not @ServiceOperation values;
  • contract and handler overloads are rejected;
  • payload parameter order and types must match;
  • extra handler parameters must be framework-injectable; and
  • generic Future<T> return types must match exactly.

Registration failures are aggregated and reported at startup rather than discovered on first call.

package com.example;
import com.example.users.GeneratedServicesModule;
import dagger.Component;
import dev.vertique.application.VertiqueApp;
import dev.vertique.application.VertiqueApplicationComponent;
import dev.vertique.starter.services.ServicesApplicationModule;
import jakarta.inject.Singleton;
@VertiqueApp
@Singleton
@Component(
modules = {
ServicesApplicationModule.class,
GeneratedServicesModule.class
})
interface ApplicationComponent extends VertiqueApplicationComponent {}

ServicesApplicationModule includes DispatchModule plus the core lifecycle and management foundation. Applications using vertique-app-parent receive the processor facade through the supported build boundary. Custom-parent builds configure vertique-codegen-all as described by the public packaging guide. If generated registration does not fit a deliberate custom binding, annotate the implementation with @NoAutoWire and contribute it manually to @Services Set<Object>. Because @NoAutoWire is source-retained, applications using that opt-out also declare dev.vertique:vertique-codegen-core with provided scope as documented in docs/packaging.md.

package com.example.users;
import jakarta.inject.Inject;
public final class UserCoordinator {
private final UserService users;
@Inject
public UserCoordinator(UserService users) {
this.users = users;
}
}

Inject and call UserService like any other asynchronous dependency. Do not construct event-bus addresses or generated proxy classes in application code.


@ServiceContract, @ServiceOperation, and @OneWay

Section titled “@ServiceContract, @ServiceOperation, and @OneWay”

These annotations define the public service protocol:

Type Purpose Important constraint
@ServiceContract Stable namespace and service name Only interfaces are contracts
@ServiceOperation Stable operation id and address segment Required for durable target resolution
@OneWay Fire-and-forget operation Method must return Future<Void>

Resilience annotations from dev.vertique.resilience.annotation may be placed on contracts or methods. Method annotations override contract-level values.

Marker interface for an implementation that keeps framework-injected values off the caller-visible contract. SecurityContext and types annotated with @DispatchContextValue may be added to handler methods when a matching receive-side decoder is installed.

create(Class<T>) selects a generated {Contract}_ServiceClientProxy companion when one is present on the contract’s classloader (emitted by vertique-codegen-services), preferring it over a JDK dynamic proxy, which is used as the fallback when no companion is present. Both paths handle DispatchEnvelope/Result encoding identically and delegate all transport to the framework; policy enforcement happens server-side.

Create-time contract completeness. Before building the proxy, create(Class<T>) resolves the contract in the registry — an unregistered contract throws IllegalArgumentException, always checked first — then validates that every non-static, non-Object-declared public method of the contract interface has a corresponding registered operation. Each method’s operation id is resolved the same way runtime dispatch resolves it (the @ServiceOperation value, or the method name when the annotation is absent) and looked up in the resolved entry’s operations map. A registry superset — extra registered operations with no corresponding interface method — is allowed. A missing operation fails create() immediately with IllegalStateException whose message begins with the exact literal "Service client contract mismatch: ", followed by the contract’s fully-qualified name, the missing operation id, and the method name.

Companion selection. After the completeness check passes, create() looks up the generated {Contract}_ServiceClientProxy companion under the contract’s own package, flattening any nested-type name (Outer.Inner → Outer_Inner). An absent companion is the expected fallback and yields the JDK dynamic proxy. A present but broken companion — a bad constructor, a failing static initializer, or a linkage error — fails loudly instead of silently falling back: the constructor’s own contract-mismatch IllegalStateException (message prefixed with the same "Service client contract mismatch: " literal) is unwrapped and rethrown unchanged, so a baked-vs-runtime drift reports identically on both paths; any other failure is wrapped as "Generated service client proxy {fqn} is present but could not be instantiated".

Common mistake: a hand-built ServiceContractContributor entry that omits an operation the contract interface declares now fails at create() time rather than only surfacing on the first invocation of that method.

The client automatically propagates registered dispatch-context values and MDC. When a SecurityContext is bound to the current Vert.x context and the matching security dispatch codecs are installed, it is captured without an explicit contract parameter.

Use the injected ServiceTargetResolver when another subsystem must persist or later resolve a service operation:

ResolvedServiceTarget target =
targetResolver.resolve("accounts.users.display-name");
String durableId = target.targetId();
String currentAddress = target.address();

Persist targetId(), not address(). Only methods with explicit @ServiceOperation ids appear in the resolver.

Include DispatchModule in the application component to install dispatch, deployment, health, context, correlation, logging, and security-event infrastructure. It also declares empty multibindings for application extension points.

When @RequiresAction is used on service contracts or operations, install the authorization engine. Vertique validates the action gates at component construction and fails closed when the required authorization bindings are absent. A typed policy that declares an action needs the same engine; role, scope, authenticated-only, public and deny policies do not (see Typed access policies).


The annotations @Timeout, @Retry, @CircuitBreaker, and @Bulkhead are defined by dev.vertique:vertique-resilience and enforced server-side by its common executor. Services translates its typed operation configuration into the same resolved policy used by the server pipeline; configuration alone never activates resilience when an operation has no annotation.

Effective policy order is timeout/circuit-breaker around retry. A configured timeout applies per attempt. @Retry.abortOn wins over retryOn; when retryOn is empty, failures not matched by abortOn are eligible for retry.

@Bulkhead limits concurrent logical executions. Its default REJECT mode fails immediately when capacity is exhausted. QUEUE mode admits a bounded FIFO queue using maxQueueSize and queueTimeoutMs; the permit covers the complete execution, including retries and backoff. The annotation may be placed on the service interface or operation, with operation declarations overriding interface declarations. Annotation inheritance follows the Java override rules: a method also inherits the annotations of a generic declaration whose type variables its class binds (delete(String) overrides Crud<ID>.delete(ID) of a Crud<String>), and of protected and same-package declarations; a same-erasure overload with another type argument, and a static or private method, override nothing.

The event-bus send timeout uses operation, service, and global sendTimeoutMs overrides before the resolved active execution budget plus the compatibility margin. Custom-backoff, unbounded, or saturated budgets require one of those explicit transport timeouts. Services has no separate bulkhead configuration; admission is enabled only by an explicit @Bulkhead declaration.

JSON configuration can override annotation values for an environment without changing the service contract. Invalid values fail during startup parsing.

An operation may select a named resilience tier with @Resilient(policy = "name"). Services layers the selected resilience.policies.<name> tier below its operation and service transport overrides, while declaration attributes remain below the named tier and family defaults remain last. A named tier does not activate resilience without the @Resilient anchor; an unknown tier fails during graph construction rather than being silently ignored.


Outbound context propagation is registry-based. Installed encoders capture typed values into the dispatch envelope; receive-side decoders restore them for the service invocation. MDC, correlation, and security integrations use this same boundary.

Prefer a handler-only SecurityContext parameter:

public Future<String> displayName(String userId, SecurityContext securityContext) {
securityContext.snapshot();
return Future.succeededFuture("User " + userId);
}

@RequiresAction adds a framework authorization gate. Authorization denials and identity-snapshot degradation failures are non-recoverable; an application interceptor cannot turn them into a successful dispatch.

A contract or operation can carry @RequiresPolicy(MyPolicy.class) instead of inline security annotations. The policy is a public interface extending AccessPolicy that declares the same requirements REST uses: @PermitAll, @DenyAll, @RolesAllowed, @Authorized (authenticated caller, optionally scopes with matchAll) and @RequiresAction. An operation policy replaces the contract policy. Mixing a policy reference with an inline security annotation, or referencing two different policies on one operation, is rejected at startup.

public interface OperatorPolicy extends AccessPolicy {} // annotated @RolesAllowed("operator")
@ServiceContract(namespace = "ops", value = "jobs")
public interface JobService {
@RequiresPolicy(OperatorPolicy.class)
Future<String> restart(String jobId);
}

Behavior for a dispatch through the generated or proxy client:

  • Local predicates run first. Any role suffices; scopes must all be held unless matchAll is false; SCOPE and PERMISSION claims both count as scopes. Claims are recomputed on every call from the real propagated SecurityContext.
  • Public and deny. @PermitAll passes with no authorization work and no event. @DenyAll denies every caller, including a call with no caller, with reason DENY_ALL.
  • Authentication. A missing or anonymous caller is denied with AUTHENTICATION_REQUIRED.
  • Action. When local checks pass and the policy declares an action, the configured Authorizer is called once with only the declared action. A local denial never reaches it.
  • Failures. A denial, a throwing or null evaluation, a null or failed future are all non-recoverable denials; recoverError cannot turn them into success.
  • Deadline. An Authorizer whose future has not settled within security.authz.gateDeadlineMs (default 5000; the same key bounds the REST, WebSocket and MCP authorization gates, which read it through the opt-in AuthorizationGateConfigModule, while the services module always reads it) is denied with INTERNAL_AUTHZ_ERROR like any other failure, through the application’s Resilience runtime. A future that is already complete is not timed. After the runtime has closed at application shutdown a pending authorization is denied at once. The deadline does not cancel the authorizer’s own work. The component graph must bind a Vertx, which DispatchModule already requires, and ServiceAuthorizationInterceptor’s constructor takes the ServiceAuthorizationConfig and the Resilience runtime as its final parameters.

Startup rules: the policy reference is resolved when the component is built, and every violation is reported in one ServiceRegistrationException. A declared action must parse and be registered, and requires the Authorizer and ActionRegistry. Role, scope, authenticated-only, public and deny policies boot without either. A dispatch that carries a policy reference with no resolved gate is denied with INTERNAL_AUTHZ_ERROR.

Caller support boundary:

  • Marked callers are rejected by local predicates. A caller whose context carries a reconstruction marker, an on-behalf-of identity subject or a delegation is denied with UNSUPPORTED_POLICY_CALLER before any claim or action evaluation, even when the action would permit. This is a support boundary, not proof that an unmarked caller is trusted.
  • Action-only policies keep the action path. A policy whose only requirement is an action does not apply the marker check; reconstruction, narrowing, assurance and delegation handling stay inside the injected Authorizer.
  • Direct implementation calls are not dispatch. Calling a service implementation as an ordinary Java method is not enforced. Only dispatch through the service client and interceptor chain is.
  • Only the contract’s policy is collected. Collection starts at the registered contract interface. A policy declared on an implementation class or a handler method is not collected and does not protect the operation. The manual registrar collects at registration and rejects a registration whose policy is invalid, conflicting or mixed with an inline security annotation. A hand-written ServiceContractContributor entry built with ServiceContractEntries collects method policies the same way when it supplies no .methodAnnotations(...) override, starting at the class passed to .contract(...), so a policy on a parent interface of an inherited method is enforced and an invalid or conflicting policy fails the build of the entry with a ServiceRegistrationException. Documented limit: class-level annotations on that path are still read from the service instance’s class (from the contract when a handler method is set), so a class-level policy declared on the implementation class is read from there and still enforces. Methods passed to .method(...) must be contract methods: a bridge or synthetic method with no unique non-bridge counterpart is rejected on this path with a ServiceRegistrationException, as the manual registrar rejects it, even when no policy is declared.
  • Application checks on resources, tenants, ownership and business state remain the application’s responsibility and run inside the service.

Event shape: every restrictive attempt emits exactly one AuthorizationDecisionEvent. For a local-only policy request.action is the descriptive operation label, not a registered action: it is never parsed as an ActionRef and never sent to the Authorizer or ActionRegistry, so an observer must not infer action registration or grant semantics from that string or the reason code alone. An allow reason can also occur on a local-only event. The request context carries the role and scope requirements. For a policy with both local predicates and an action, one composed event is emitted whose reason is the first failing predicate and whose safeAttributes record rolesSatisfied, actionSatisfied and actionEvaluated; request.action is still the operation label. rolesSatisfied reflects the whole local predicate, that is authentication, roles and scopes together, not roles alone. actionEvaluated says whether the action step ran (it does not when a local predicate failed) and actionSatisfied says whether that step permitted. An action-only policy emits the existing action event with the declared action.


DispatchModule includes SecurityEventsModule from dev.vertique:vertique-security-runtime. Every module it pulls in is Stable, so installing it commits an application only to Stable wiring.

Contribute ordered cross-cutting behavior through Dagger multibinding:

@Provides
@IntoSet
static ServiceInterceptor serviceMetrics(ServiceMetricsInterceptor interceptor) {
return interceptor;
}

Callbacks have distinct semantics:

Callback Role Can change the dispatch outcome?
beforeDispatch Async enrichment or precondition Yes; failure short-circuits
onDispatch Synchronous pre-dispatch observation No
onComplete Observe handler outcome before recovery No
afterDispatch Async post-processing No; failures are logged
recoverError Attempt application recovery Yes; first successful recovery wins
onTerminalComplete Observe final post-recovery outcome No

Interceptors follow the OrderedExtension order: phase, ascending priority, then stable orderKey. Use onComplete for the raw handler outcome and onTerminalComplete for the final outcome after recovery; they are intentionally different signals.

Contribute exception translation at the service boundary:

@Provides
@IntoSet
static ServiceExceptionMapperCustomizer serviceErrors() {
return mapper -> mapper.on(
IllegalArgumentException.class,
error -> new IllegalStateException(error.getMessage(), error));
}

Customizers follow OrderedExtension; registrations applied later win for the same exception type.

This advanced SPI lets another framework module register service-shaped handlers without @ServiceContract discovery. Build entries with ServiceContractEntries; do not instantiate dispatch metadata records directly. Contributors participate in the same collision validation, deployment, policies, and lifecycle as application contracts.

Use generated service registration for normal applications.

Manual registration remains available for implementations that intentionally opt out of codegen:

@Provides
@IntoSet
@Services
static Object userService(UserServiceHandler handler) {
return handler;
}

This is an explicit fallback, not an additional registration to combine with the generated entry.


Configuration lives under services. The empty namespace uses _ as its configuration key.

{
"services": {
"sendTimeoutMs": 30000,
"contracts": {
"accounts": {
"users": {
"instances": 2,
"worker": false,
"sendTimeoutMs": 10000,
"supervision": {
"maxRestarts": 5,
"withinMs": 60000,
"initialBackoffMs": 1000,
"maxBackoffMs": 30000
},
"operations": {
"display-name": {
"sendTimeoutMs": 5000,
"timeout": {
"valueMs": 2000
},
"retry": {
"maxRetries": 2,
"delayMs": 50,
"backoffMultiplier": 2.0,
"maxDelayMs": 1000
},
"circuitBreaker": {
"maxFailures": 5,
"timeoutMs": 2000,
"resetTimeoutMs": 30000
}
}
}
}
}
}
}
}
Key Default Constraint
services.sendTimeoutMs 30000 greater than 0
security.authz.gateDeadlineMs 5000 greater than 0, a number or a numeric string; the authorizer-call deadline, the key the REST, WebSocket and MCP gates also read
contracts.{ns}.{name}.instances 1 at least 1
contracts.{ns}.{name}.worker false boolean
contracts.{ns}.{name}.sendTimeoutMs global value greater than 0
supervision.maxRestarts 5 see deploy supervision contract
supervision.withinMs 60000 milliseconds
supervision.initialBackoffMs 1000 milliseconds
supervision.maxBackoffMs 30000 milliseconds
operations.{op}.sendTimeoutMs service value greater than 0
operations.{op}.timeout.valueMs annotation value greater than 0
operations.{op}.retry.maxRetries annotation value at least 0
operations.{op}.retry.delayMs annotation value at least 0
operations.{op}.retry.backoffMultiplier annotation value at least 1.0
operations.{op}.retry.maxDelayMs annotation value at least 0
operations.{op}.circuitBreaker.maxFailures annotation value at least 1
operations.{op}.circuitBreaker.timeoutMs annotation value greater than 0
operations.{op}.circuitBreaker.resetTimeoutMs annotation value greater than 0

Operation keys are resolved operation ids: the @ServiceOperation value when present, otherwise the Java method name.


Failures, Constraints, and Common Mistakes

Section titled “Failures, Constraints, and Common Mistakes”
  • Service contracts must be interfaces and operations must return parameterized Future<T>.
  • Contract and handler overloads are rejected; operation identity must be unambiguous.
  • A contract operation has at most one payload parameter. DispatchEnvelope<?> is a transport wrapper and is not a valid contract parameter.
  • @OneWay is best-effort and does not provide delivery or processing confirmation.
  • Do not block the event loop. Configure a worker service only when the handler genuinely performs blocking work.
  • Do not persist event-bus addresses. Persist stable target ids from explicitly annotated operations.
  • Do not bind a generated and manual implementation for the same contract.
  • Do not use interceptor recovery for authorization or identity-degradation failures; Vertique marks those failures non-recoverable.
  • Do not place @RequiresPolicy on a service implementation class or handler method; only the contract’s declaration is collected and enforced.
  • Do not expect typed role or scope policies to admit reconstructed, on-behalf-of or delegated callers; they are denied with UNSUPPORTED_POLICY_CALLER.
  • A hand-built ServiceContractContributor entry that omits an operation the contract interface declares now fails at ServiceClientFactory.create() time, not only on the first invocation of that method.

Transport failures are enriched as service failures:

Failure Meaning
ServiceTimeoutException request/reply exceeded the effective send timeout
ServiceUnavailableException supervisor unavailable or no event-bus handler
ServiceDispatchException other service transport failure
ServiceRegistrationException one or more contract/handler validation failures at startup

For a focused module check from the repository root:

Terminal window
./mvnw -ntp -pl vertique-services -am test

The public examples examples/vertique-example-services and examples/vertique-example-services-codegen demonstrate generated registration, handler implementations, typed clients, authorization, and lifecycle wiring.


  • dev.vertique:vertique-core — async results, event-bus envelopes, lifecycle, and ordered extensions.
  • dev.vertique:vertique-resilience — canonical resilience annotations, declaration metadata, and retry contracts.
  • dev.vertique:vertique-context — typed dispatch-context capture and restoration.
  • dev.vertique:vertique-correlation and dev.vertique:vertique-logging — correlation and MDC propagation.
  • dev.vertique:vertique-deploy — verticle deployment, supervision, and lifecycle integration.
  • dev.vertique:vertique-security-core and dev.vertique:vertique-security-runtime — security context, action authorization, identity-degradation policy, and security events.
  • io.vertx:vertx-core — event-bus transport; resilience execution is owned by vertique-resilience.
  • Dagger and Jakarta Inject — application wiring and extension multibindings.