HelidonHelidon4.5.6

LRA

MicroProfile Long Running Actions

Overview

Distributed transactions for microservices are known as SAGA design patterns and are defined by the MicroProfile Long Running Actions specification. Unlike well known XA protocol, LRA is asynchronous and therefore much more scalable. Every LRA JAX-RS resource (participant) defines endpoints to be invoked when transaction needs to be completed or compensated.

Maven Coordinates

To enable Long Running Actions, add the following dependency to your project’s pom.xml (see Managing Dependencies).

pom.xml
<dependencies>
  <dependency>
    <groupId>io.helidon.microprofile.lra</groupId>
    <artifactId>helidon-microprofile-lra</artifactId>
  </dependency>
  <!-- Support for Narayana coordinator -->
  <dependency>
    <groupId>io.helidon.lra</groupId>
    <artifactId>helidon-lra-coordinator-narayana-client</artifactId>
  </dependency>
</dependencies>

Usage

The LRA transactions need to be coordinated over REST API by the LRA coordinator. Coordinator keeps track of all transactions and calls the @Compensate or @Complete endpoints for all participants involved in the particular transaction. LRA transaction is first started, then joined by participant. The participant reports the successful finish of the transaction by calling it complete. The coordinator then calls the JAX-RS complete endpoint that was registered during the join of each participant. As the completed or compensated participants don’t have to be on same instance, the whole architecture is highly scalable.

If an error occurs during the LRA transaction, the participant reports a cancellation of LRA to the coordinator. Coordinator calls compensate on all the joined participants.

When a participant joins the LRA with timeout defined @LRA(value = LRA.Type.REQUIRES_NEW, timeLimit = 5, timeUnit = ChronoUnit.MINUTES), the coordinator compensates if the timeout occurred before the close is reported by the participants.

API

Participant

The Participant, or Compensator, is an LRA resource with at least one of the JAX-RS(or non-JAX-RS) methods annotated with @Compensate or @AfterLRA.

@LRA

See the Javadoc.

Marks JAX-RS method which should run in LRA context and needs to be accompanied by at least minimal set of mandatory participant methods(Compensate or AfterLRA).

LRA options:

  • value
    • REQUIRED join incoming LRA or create and join new
    • REQUIRES_NEW create and join new LRA
    • MANDATORY join incoming LRA or fail
    • SUPPORTS join incoming LRA or continue outside LRA context
    • NOT_SUPPORTED always continue outside LRA context
    • NEVER Fail with 412 if executed in LRA context
    • NESTED create and join new LRA nested in the incoming LRA context
  • timeLimit max time limit before LRA gets cancelled automatically by coordinator
  • timeUnit time unit if the timeLimit value
  • end when false LRA is not closed after successful method execution
  • cancelOn which HTTP response codes of the method causes LRA to cancel
  • cancelOnFamily which family of HTTP response codes causes LRA to cancel

Method parameters:

@PUT
@LRA(value = LRA.Type.REQUIRES_NEW,
     timeLimit = 500,
     timeUnit = ChronoUnit.MILLIS)
@Path("start-example")
public Response startLra(
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
        String data) {
    return Response.ok().build();
}

@Compensate

See the Javadoc.

Expected to be called by LRA coordinator only!

Compensate method is called by a coordinator when LRA is cancelled, usually by error during execution of method body of @LRA annotated method. If the method responds with 500 or 202, coordinator will eventually try the call again. If participant has @Status annotated method, coordinator retrieves the status to find out if retry should be done.

JAX-RS variant with supported LRA context values:

@PUT
@Path("/compensate")
@Compensate
public Response compensateWork(
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
        @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parent) {
    return LRAResponse.compensated();
}

Non JAX-RS variant with supported LRA context values:

  • URI with LRA ID
@Compensate
public void compensate(URI lraId) {
}

@Complete

See the Javadoc.

Expected to be called by LRA coordinator only!

Complete method is called by coordinator when LRA is successfully closed. If the method responds with 500 or 202, coordinator will eventually try the call again. If participant has @Status annotated method, coordinator retrieves the status to find out if retry should be done.

JAX-RS variant with supported LRA context values:

@PUT
@Path("/complete")
@Complete
public Response complete(
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
        @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parentLraId) {
    return LRAResponse.completed();
}

Non JAX-RS variant with supported LRA context values:

  • URI with LRA ID
@Complete
public void complete(URI lraId) {
}

@Forget

See the Javadoc.

Expected to be called by LRA coordinator only!

Complete and compensate methods can fail(500) or report that compensation/completion is in progress(202). In such case participant needs to be prepared to report its status over @Status annotated method to coordinator. When coordinator decides all the participants have finished, method annotated with @Forget is called.

JAX-RS variant with supported LRA context values:

@DELETE
@Path("/forget")
@Forget
public Response forget(
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
        @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parent) {
    return Response.noContent().build();
}

Non JAX-RS variant with supported LRA context values:

  • URI with LRA ID
@Forget
public void forget(URI lraId) {
}

@Leave

See the Javadoc.

Method annotated with @Leave called with LRA context(with header LRA_HTTP_CONTEXT_HEADER) informs coordinator that current participant is leaving the LRA. Method body is executed after leave signal is sent. As a result, participant methods complete and compensate won’t be called when the particular LRA ends.

Applications that declare LRA participant callbacks must configure mp.lra.participant.url. This ensures the leave request identifies the same participant links that were registered when the application joined the LRA. When endpoint mappings are configured, send @Leave through a deployment identity that resolves to the same canonical callback base used to join the participant.

@PUT
@Path("/leave")
@Leave
public Response leaveLRA(
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraIdtoLeave) {
    return Response.ok().build();
}

@Status

See the Javadoc.

Expected to be called by LRA coordinator only!

If the coordinator’s call to the participant’s method fails, then it will retry the call. If the participant is not idempotent, then it may need to report its state to coordinator by declaring method annotated with @Status for reporting if previous call did change participant status. Coordinator can call it and decide if compensate or complete retry is needed.

JAX-RS variant with supported LRA context values:

@GET
@Path("/status")
@Status
public Response reportStatus(
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
    return Response.ok(ParticipantStatus.FailedToCompensate)
        .build();
}

Non JAX-RS variant with supported LRA context values:

@Status
public Response reportStatus(URI lraId) {
    return Response.ok(ParticipantStatus.FailedToCompensate)
        .build();
}

@AfterLRA

See the Javadoc.

Expected to be called by LRA coordinator only!

Method annotated with @AfterLRA in the same class as the one with @LRA annotation gets invoked after particular LRA finishes.

JAX-RS variant with supported LRA context values:

@PUT
@Path("/finished")
@AfterLRA
public Response whenLRAFinishes(
        @HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId,
        @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parentLraId,
        LRAStatus status) {
    return Response.ok().build();
}

Non JAX-RS variant with supported LRA context values:

public void whenLRAFinishes(URI lraId, LRAStatus status) {
}

Configuration

Configuration options:

KeyTypeDefault valueDescription
mp.lra.coordinator.urlstringhttp://localhost:8070/lra-coordinatorUrl of coordinator.
mp.lra.coordinator.trusted-urlsstring listAdditional trusted coordinator base URLs accepted from propagated LRA identifiers. An identifier must have the same scheme, host, effective port, and immediate parent path as a configured base. Exact LRA identifiers returned by successful start requests are trusted automatically for the client instance until reinitialization or eviction from the 10,000-entry learned-identifier cache.
mp.lra.coordinator.propagation.activebooleanPropagate LRA headers LRA_HTTP_CONTEXT_HEADER and LRA_HTTP_PARENT_CONTEXT_HEADER through non-LRA endpoints.
mp.lra.participant.urlstringDefault canonical, externally reachable base URL for participant callbacks. This property is required when the application declares an LRA participant callback, whether JAX-RS or non-JAX-RS. The value must be an absolute HTTP or HTTPS URI with a host and may include a path and port, but not user info, query, or fragment. It is also the base URL for every non-JAX-RS callback. An explicit port 0 uses the bound request listener for JAX-RS callbacks and the default listener for non-JAX-RS callbacks, and is intended for direct-listener tests; configure the actual public port when callbacks pass through a proxy or load balancer.
mp.lra.participant.endpoints.*objectExact JAX-RS callback base URLs for applications with distinct deployment identities. Each named entry contains listener, application-path, and url. The listener and route-effective application base path select the entry; the URL is used in full and follows the same URI validation rules as mp.lra.participant.url. Configure an entry for each identity when the deployment has multiple JAX-RS application roots or named listeners.
mp.lra.coordinator.timeoutstringTimeout for synchronous communication with coordinator.
mp.lra.coordinator.timeout-unitstringTimeout unit for synchronous communication with coordinator.
lra.participant.non-jax-rs.context-pathstring/lra-participantContext path for non-JAX-RS participant callbacks.
lra.participant.non-jax-rs.callback-auth.secretstringBase64 URL-encoded callback-signing secret. This property is required when the application declares a non-JAX-RS participant callback. The decoded secret must contain at least 32 bytes.
lra.participant.non-jax-rs.callback-auth.compatibility-modebooleanfalseUse unsigned callback URLs and accept unsigned callback requests. Enable this property only temporarily while upgrading from a version that does not sign callback URLs.

Example of LRA configuration:

mp.lra:
  coordinator.url: http://localhost:8070/lra-coordinator <1>
  coordinator.trusted-urls: https://secondary.example/lra-coordinator <2>
  propagation.active: true <3>
  participant.url: https://coordinator.visible.host:443/awesomeapp <4>
  1. Url of coordinator
  2. Additional trusted coordinator base URL for propagated LRA identifiers
  3. Propagate LRA headers LRA_HTTP_CONTEXT_HEADER and LRA_HTTP_PARENT_CONTEXT_HEADER through non-LRA endpoints
  4. Canonical, externally reachable URL of the LRA-enabled service

Up to 10,000 recently used learned LRA identifiers are held in memory; older entries are evicted. Configure every coordinator base that may appear in a propagated identifier when more identifiers must remain trusted, an LRA can reach another service instance, survive an application restart, or use a direct coordinator node URL returned through a load balancer. URI scheme and DNS host comparisons are case-insensitive, omitted HTTP and HTTPS ports are treated as 80 and 443, and the LRA identifier must be an immediate child of the configured base path. Coordinator and LRA identifier paths used for this trust check must not contain user info, fragments, backslashes, matrix parameters (;), encoded matrix delimiters (%3b), encoded dot, slash, or backslash characters, or dot segments that would change the normalized path. Configured trusted coordinator base URLs must not contain a query. Coordinator redirects are not followed; configured and trusted URLs must identify the final coordinator endpoints.

Canonical Participant URL

Helidon uses each configured participant URL as a complete callback base. It never uses an incoming request's scheme or authority. The default mp.lra.participant.url can serve one JAX-RS deployment identity and is always the base for non-JAX-RS callbacks. A deployment identity consists of the listener name and the route-effective JAX-RS application base path.

Configure exact mp.lra.participant.endpoints entries when a deployment has multiple application roots or named listeners. Entry names such as orders and admin are descriptive only. The listener and application-path fields form the lookup key, and url is the complete external application base. Application paths must begin with /; trailing slashes are ignored when matching. Percent encoding follows WebServer route-pattern semantics for endpoint application paths and request-path semantics for the default URL. The resulting paths must identify the same route-effective application base. The configured URL path is used unchanged, preserving its encoding. The reserved listener name @default identifies Helidon's default listener. Callback selection for one deployment identity does not depend on which configured identity receives the first request. If aliases for the same resource class represent one participant, configure them with the same resolved canonical callback base.

mp.lra:
  participant:
    url: https://public.example/default-app
    endpoints:
      orders:
        listener: "@default"
        application-path: /orders
        url: https://public.example/orders
      admin:
        listener: admin
        application-path: /admin
        url: https://admin.example:8443/admin

If no endpoint entry matches, Helidon uses the default URL only for the default listener and only when its route-effective path equals the JAX-RS application base path. Other unmatched identities fail with HTTP 500 instead of registering callbacks to the wrong application. Configure an explicit endpoint when a named listener or an external proxy path makes that exact default mapping unsuitable.

Use a fixed public port in production. Port 0 resolves to the listener that received a JAX-RS request, including a named listener, and to the default listener for non-JAX-RS callbacks. That behavior is intended for tests that connect directly to Helidon; it cannot discover a proxy or load balancer's public port.

Upgrading JAX-RS Participants

For a deployment with one JAX-RS identity, if the full application base previously advertised to the coordinator is stable, configure that exact mp.lra.participant.url on the old version before upgrading. If its external path differs from the internal JAX-RS application base path, also preconfigure the matching endpoint entry; the old version ignores the entry and upgraded nodes use it. Verify that callback links remain unchanged, then upgrade replicas one at a time with the same values. JAX-RS-only participants do not need a callback-signing secret or compatibility mode.

If the previously advertised origin and path cannot be preserved or proven, for example because requests used multiple Host aliases or a proxy path is changing, use a drain-first upgrade:

  1. On the old version, quiesce requests that can start LRAs, join participants, or leave LRAs. Keep completion, compensation, and other callback traffic available.
  2. Wait until every existing participant registration finishes.

For a JAX-RS-only deployment, configure the default URL and every required endpoint entry after the registrations have drained, upgrade the replicas, and then resume LRA traffic. Endpoint entries are not understood by older releases, so do not rely on them to preserve a multi-identity rolling upgrade.

The rolling procedure above applies to JAX-RS-only deployments. If the deployment also uses non-JAX-RS callbacks and can preserve its participant links, use the authentication-specific rolling procedure below instead; it configures the signing secret and compatibility mode before any replica is upgraded. If the links cannot be preserved, drain the registrations using the two steps above, then configure the default URL, every required endpoint, and the signing secret before upgrading. Compatibility mode accepts legacy unsigned non-JAX-RS callbacks; it does not migrate JAX-RS participant links.

Non-JAX-RS Callback Authentication

Helidon authenticates non-JAX-RS participant callbacks with a capability embedded in each callback URL. The capability is bound to the LRA identifier, callback type, participant class, and participant method. Applications that declare non-JAX-RS participant callbacks must configure a canonical callback origin using mp.lra.participant.url and lra.participant.non-jax-rs.callback-auth.secret with at least 32 bytes of Base64 URL-encoded secret material.

Helidon does not derive participant callback URLs from the authority of an incoming request. Generate the secret with a cryptographically secure random number generator and use a separate secret for each application deployment. Share the secret only among replicas that serve the same callbacks. Store the secret in a protected configuration source rather than in application source control.

Callback URLs contain authentication material and must be handled as credentials. Use TLS, or an equivalently protected trusted transport, for both coordinator registration and participant callback traffic. Avoid recording complete callback URLs in application, proxy, or access logs. Do not change the signing secret until all LRAs registered with the existing secret have drained. Changing it alters the registered callback URLs and can also prevent an @Leave request from matching the participant registration.

mp.lra:
  participant.url: "https://participant.example/application"
lra.participant.non-jax-rs.callback-auth:
  secret: "<base64-url-encoded-secret>"
  compatibility-mode: false

Rolling Upgrade from Unsigned Non-JAX-RS Callbacks

The compatibility setting supports a rolling upgrade from a version that registered unsigned non-JAX-RS callback URLs. Compatibility mode deliberately preserves the previous behavior: upgraded nodes both generate and accept unsigned callback URLs. A request containing an invalid capability is rejected even in compatibility mode; only a request without a capability receives legacy handling.

If the canonical participant URL cannot preserve existing participant links, first drain the registrations using the two numbered steps in Upgrading JAX-RS Participants. Then configure the canonical URL and signing secret before upgrading; compatibility mode is unnecessary because no unsigned registrations remain.

When the exact origin and path already registered with the coordinator can be preserved, use this rolling procedure:

  1. Configure the same canonical mp.lra.participant.url and signing secret, and set compatibility-mode to true for every replica. Preserve the participant origin and path already advertised to the coordinator; changing participant links prevents @Leave from matching an in-flight registration. The previous Helidon version ignores the callback-authentication settings.
  2. Upgrade the replicas one at a time. Keep compatibility mode enabled on every upgraded replica.
  3. After all replicas run the new version, quiesce requests that can start LRAs or register participants. Keep completion, compensation, callback, and leave traffic available so existing LRAs can finish.
  4. Wait for every LRA registered before or during compatibility mode to finish and verify that no compatibility-era participant registrations remain.
  5. Set compatibility-mode to false and restart all replicas. Replicas may restart one at a time, but LRA creation and participant registration must remain quiesced until every replica uses strict mode.
  6. Resume LRA traffic. New callback URLs are signed and unsigned callbacks are rejected.

For more information continue to MicroProfile Long Running Actions specification.

Examples

The following example shows how a simple LRA participant starts and joins a transaction after calling the '/start-example' resource. When startExample method finishes successfully, close is reported to coordinator and /complete-example endpoint is called by coordinator to confirm successful closure of the LRA.

If an exception occurs during startExample method execution, coordinator receives cancel call and /compensate-example is called by coordinator to compensate for cancelled LRA transaction.

Example of simple LRA participant:

@PUT
@LRA(LRA.Type.REQUIRES_NEW)1
@Path("start-example")
public Response startExample(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,2
                             String data) {
    if (data.contains("BOOM")) {
        throw new RuntimeException("BOOM 💥");3
    }

    LOGGER.info("Data " + data + " processed 🏭");
    return Response.ok().build();4
}

@PUT
@Complete5
@Path("complete-example")
public Response completeExample(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
    LOGGER.log(Level.INFO, "LRA ID: {0} completed 🎉", lraId);
    return LRAResponse.completed();
}

@PUT
@Compensate6
@Path("compensate-example")
public Response compensateExample(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
    LOGGER.log(Level.SEVERE, "LRA ID: {0} compensated 🦺", lraId);
    return LRAResponse.compensated();
}
  1. This JAX-RS PUT method will start new LRA transactions and join it before method body gets executed
  2. LRA ID assigned by coordinator to this LRA transaction
  3. When method execution finishes exceptionally, cancel signal for this particular LRA is sent to coordinator
  4. When method execution finishes successfully, complete signal for this particular LRA is sent to coordinator If the after-resource cancel or complete coordinator call fails, Helidon replaces the resource response with the coordinator failure status. Coordinator redirects are not followed in this phase; a coordinator redirect is reported to the caller as 502 Bad Gateway without a Location header.
  5. Method which will be called by coordinator when LRA is completed
  6. Method which will be called by coordinator when LRA is canceled

Testing

Testing of JAX-RS resources with LRA can be challenging as LRA participant running in parallel with the test is needed.

Helidon provides test coordinator which can be started automatically with additional socket on a random port within your own Helidon application. You only need one extra test dependency to enable test coordinator in your @HelidonTest.

Dependency:

pom.xml
<dependency>
  <groupId>io.helidon.microprofile.lra</groupId>
  <artifactId>helidon-microprofile-lra-testing</artifactId>
  <scope>test</scope>
</dependency>

Considering that you have LRA enabled JAX-RS resource you want to test.

Example JAX-RS resource with LRA:

@ApplicationScoped
@Path("/test")
public class WithdrawResource {

    private final List<String> completedLras = new CopyOnWriteArrayList<>();
    private final List<String> cancelledLras = new CopyOnWriteArrayList<>();

    @PUT
    @Path("/withdraw")
    @LRA(LRA.Type.REQUIRES_NEW)
    public Response withdraw(
            @HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) Optional<URI> lraId,
            String content) {
        if ("BOOM".equals(content)) {
            throw new IllegalArgumentException("BOOM");
        }
        return Response.ok().build();
    }

    @Complete
    public void complete(URI lraId) {
        completedLras.add(lraId.toString());
    }

    @Compensate
    public void rollback(URI lraId) {
        cancelledLras.add(lraId.toString());
    }

    public List<String> getCompletedLras() {
        return completedLras;
    }
}

Helidon test with enabled CDI discovery can look like this.

HelidonTest with LRA test support:

@HelidonTest
//@AddBean(WithdrawResource.class)1
@AddBean(TestLraCoordinator.class)2
public class LraTest {

    @Inject
    private WithdrawResource withdrawTestResource;

    @Inject
    private TestLraCoordinator coordinator;3

    @Inject
    private WebTarget target;

    @Test
    public void testLraComplete() {
        try (Response res = target
                .path("/test/withdraw")
                .request()
                .put(Entity.entity("test", MediaType.TEXT_PLAIN_TYPE))) {
            assertThat(res.getStatus(), is(200));
            String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
            Lra lra = coordinator.lra(lraId);4
            assertThat(lra.status(), is(LRAStatus.Closed));5
            assertThat(withdrawTestResource.getCompletedLras(), contains(lraId));
        }
    }
}
  1. Resource is discovered automatically
  2. Test coordinator needs to be added manually
  3. Injecting test coordinator to access state of LRA managed by coordinator mid-test
  4. Retrieving LRA managed by coordinator by LraId
  5. Asserting LRA state in coordinator

LRA testing feature has the following default configuration:

  • port: 0 - coordinator is started on random port(Helidon LRA participant is capable to discover test coordinator automatically)
  • bind-address: localhost - bind address of the coordinator
  • helidon.lra.coordinator.persistence: false - LRAs managed by test coordinator are not persisted
  • helidon.lra.participant.use-build-time-index: false - Participant annotation inspection ignores Jandex index files created in build time, it helps to avoid issues with additional test resources
  • mp.lra.participant.url: http://localhost:0 - participant callbacks use the random default server listener port; override this setting with @AddConfig when callbacks use a proxy, load balancer, or another public origin

Testing LRA coordinator is started on additional named socket test-lra-coordinator configured with default index 500. Default index can be changed with system property helidon.lra.coordinator.test-socket.index.

Example: -Dhelidon.lra.coordinator.test-socket.index=20.

HelidonTest override LRA test feature default settings:

@HelidonTest
@AddBean(TestLraCoordinator.class)
@AddConfig(key = "server.sockets.500.port", value = "8070")1
@AddConfig(key = "server.sockets.500.host", value = "custom.bind.name")2
@AddConfig(key = "helidon.lra.coordinator.persistence", value = "true")3
@AddConfig(key = "helidon.lra.participant.use-build-time-index", value = "true")4
public class LraCustomConfigTest {
}
  1. Start test LRA coordinator always on the same port 8070(default is random port)
  2. Test LRA coordinator socket bind address (default is localhost)
  3. Persist LRA managed by coordinator(default is false)
  4. Use build time Jandex index(default is false)

When CDI bean auto-discovery is not desired, LRA and Config CDI extensions needs to be added manually.

HelidonTest setup with disabled discovery:

@HelidonTest
@DisableDiscovery
@AddJaxRs
@AddBean(TestLraCoordinator.class)
@AddExtension(LraCdiExtension.class)
@AddExtension(ConfigCdiExtension.class)
@AddBean(WithdrawResource.class)
public class LraNoDiscoveryTest {
}

Coordinator

Coordinator is a service that tracks all LRA transactions and calls the compensate REST endpoints of the participants when the LRA transaction gets cancelled or completes (in case it gets closed). In addition, participant also keeps track of timeouts, retries participant calls, and assigns LRA ids.

Helidon LRA supports following coordinators:

MicroTx Coordinator

Oracle Transaction Manager for Microservices - MicroTx is an enterprise grade transaction manager for microservices, among other it manages LRA transactions and is compatible with Narayana LRA clients.

MicroTx LRA coordinator is compatible with Narayana clients when narayanaLraCompatibilityMode is on, you need to add another dependency to enable Narayana client:

Dependency needed for using Helidon LRA with Narayana compatible coordinator:

pom.xml
<dependency>
  <groupId>io.helidon.lra</groupId>
  <artifactId>helidon-lra-coordinator-narayana-client</artifactId>
</dependency>

Run MicroTx in Docker:

Terminal
docker container run --name otmm -v "$(pwd)":/app/config \
-w /app/config -p 8080:8080/tcp --env CONFIG_FILE=tcs.yaml \
--add-host host.docker.internal:host-gateway -d tmm:<version>

To use MicroTx with Helidon LRA participant, narayanaLraCompatibilityMode needs to be enabled.

Configure MicroTx for development:

tmmAppName: tcs
tmmConfiguration:
  listenAddr: 0.0.0.0:8080
  internalAddr: http://host.docker.internal:8080
  externalUrl: http://lra-coordinator.acme.com:8080
  xaCoordinator:
    enabled: false
  lraCoordinator:
    enabled: true
  tccCoordinator:
    enabled: false
  storage:
    type: memory
  authentication:
    enabled: false
  authorization:
    enabled: false
  serveTLS:
    enabled: false
  narayanaLraCompatibilityMode:
    enabled: true1
  1. Enable Narayana compatibility mode

Helidon Coordinator

Test tool, usage in production is not advised.

Build and run Helidon LRA coordinator:

Terminal
docker build -t helidon/lra-coordinator https://github.com/helidon-io/helidon.git#:lra/coordinator/server
docker run --name lra-coordinator --network="host" helidon/lra-coordinator

Helidon LRA coordinator is compatible with Narayana clients, you need to add a dependency for Narayana client:

Dependency needed for using Helidon LRA with Narayana compatible coordinator:

pom.xml
<dependency>
  <groupId>io.helidon.lra</groupId>
  <artifactId>helidon-lra-coordinator-narayana-client</artifactId>
</dependency>

Narayana

Narayana is a transaction manager supporting LRA. To use Narayana LRA coordinator with Helidon LRA client you need to add a dependency for Narayana client:

Dependency needed for using Helidon LRA with Narayana coordinator:

pom.xml
<dependency>
  <groupId>io.helidon.lra</groupId>
  <artifactId>helidon-lra-coordinator-narayana-client</artifactId>
</dependency>

The simplest way to run Narayana LRA coordinator locally:

Downloading and running Narayana LRA coordinator:

Terminal
curl https://repo1.maven.org/maven2/org/jboss/narayana/rts/lra-coordinator-quarkus/5.11.1.Final/lra-coordinator-quarkus-5.11.1.Final-runner.jar \
-o narayana-coordinator.jar
java -Dquarkus.http.port=8070 -jar narayana-coordinator.jar

Narayana LRA coordinator is running by default under lra-coordinator context, with port 8070 defined in the snippet above you need to configure your Helidon LRA app as follows: mp.lra.coordinator.url=http://localhost:8070/lra-coordinator

Reference

Copyright © 2018, 2026 Oracle and/or its affiliates.