Wednesday, July 14, 2021

Narayana LRA Update

Introduction

This is another in a series of blogs about the compensation based approach to transactions that the team have been producing over the years. The latest such model is LRA (Long Running Actions), originally based on the 2006 OASIS LRA spec, which was recently been accepted by the Eclipse Foundation. The specification and the javadoc for the annotation API are also available.

Although LRA is a simple protocol it has a number of interesting features and one blog won’t do it justice. In this, the first one, I will describe how to create a simple microservice that executes in the context of an LRA. Section 1 explains how to create and run an LRA coordinator, section 2 describes how to create and run a participant, and in the final section there is a short review of the many blogs on the subject that the team have created during the past decade. These blogs are an excellent source of wisdom so I will try to avoid repeating old ground and refer the reader to those blogs for the details of the general approach (of which MP-LRA is just the latest incarnation).

In follow up blogs the team and I plan to cover, in no particular order:

  • how to participate in failure recovery (including participant, coordinator and network failures)

  • writing participants in languages other than Java

  • nesting LRA’s to structure business flows (into hierarchies)

  • various methods of triggering the cancellation of an LRA (resulting in the reliable invocation all compensation activities)

  • leaving LRA’s early

  • inspecting the progress of participants

  • inspecting failed participants (i.e. ones which have finished in a failed state)

  • restarting crashed participants on different endpoints

  • show services interacting with each other in different JVMs

  • show services running on OpenShift

  • leveraging quarkus and WildFly features to simplify the development process (using extensions and galleon feature packs)

  • addressing the demands that cloud infrastructures, such as OpenShift, place on LRA’s

  • investigate some best practices and future plans for managing the availability of coordinators and participants (including participant storage, different storage types such as databases and journals, and scaling of coordinators)

  • strategies for writing compensation logic

  • and I’m sure my colleagues will have plenty of other topics to add to this list.

The Example

A Long Running Action is an interaction between microservices such that all parties (called LRA participants) are guaranteed to be notified when the interaction finishes (in either a successful closing state or an unsuccessful cancelling state). A JAX-RS resource participates in an interaction by marking one or more of its methods with the @LRA annotation and by marking another of its methods with an @Compensate annotation. When a method marked with @LRA is invoked the resource is enlisted in the LRA. Enlisting with an LRA means that if the associated LRA is cancelled then the method annotated with @Compensate is invoked reliably (i.e. it will continue to be called until it is definite that the method executed successfully and that the coordinator received the response). The resource may also request that it be reliably notified if the LRA is closed by marking one of its methods with an @Complete annotation. Note that the LRA id is available to all annotated methods so that all parties know which context is Active.

In order to implement the guarantees stated in the previous paragraph, the narayana implementation requires that there are one or more LRA coordinators running in the system. A coordinator runs on behalf of many services and is responsible for starting and ending LRA’s and for managing the participant membership in the LRA, in other words it must be available for an interaction to progress (start, enlist and end). Similarly, participant resources must be available during the end phase of the LRA so they too must be restarted if they fail.

Note that the developer is normally unconcerned with the coordinator and a typical installation will run them as part of the platform, freeing up the developer to concentrate on the business of creating microservices. However, for the purposes of the blog, first I’ll indicate how you can create one. Note that there is a similar example in our quickstart repo. Later on we will make the blog examples available in the same repo.

Starting a coordinator

Here we show how to build and run a REST based coordinator from scratch as a java executable using the quarkus framework.

The Narayana LRA coordinator is a JAX-RS resource so it needs the quarkus resteasy extension and it needs to depend on the Narayana LRA coordinator implementation.

First generate a quarkus application using the quarkus-maven-plugin, specifying the resteasy-jackson and rest-client extensions which pull in everything we need for JAX-RS:

mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=narayana-lra-coordinator \
    -Dextensions="resteasy-jackson,rest-client"
cd narayana-lra-coordinator

To obtain coordinator support add the org.jboss.narayana.rts:lra-coordinator-jar:5.12.0.Final maven dependency to the dependencies section of the generated pom.xml file as follows:

    <dependency>
        <groupId>org.jboss.narayana.rts</groupId>
        <artifactId>lra-coordinator-jar</artifactId>
        <version>5.12.0.Final</version>
    </dependency>

Here I have chosen the latest release (5.12.0.Final) of the Narayana LRA coordinator. Because we just need the quarkus framework for running the coordinator, remove the generated example: rm -rf src.

Now build and start the coordinator on port 8080:

rm -rf src
mvn clean package
java -Dquarkus.http.port=8080 -jar target/quarkus-app/quarkus-run.jar &

If you want to check that the coordinator is running try listing the active LRA’s:

curl http://localhost:8080/lra-coordinator

By default the coordinator stores records in the filesystem in a directory called ObjectStore in the user directory (i.e. the value of the system property user.dir). You can change the location by adding a file called src/main/resources/jbossts-properties.xml with content:

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <!-- unique id of an LRA coordinator -->
    <entry key="CoreEnvironmentBean.nodeIdentifier">1</entry>
    <!-- location of the LRA logs -->
    <entry key="ObjectStoreEnvironmentBean.objectStoreDir">target/lra-logs</entry>
    <!-- location of the communications store -->
    <entry key="ObjectStoreEnvironmentBean.communicationStore.objectStoreDir">target/lra-logs</entry>
</properties>

You can test the coordinator is operating correctly by trying to create an LRA using curl, for example.

curl -XPOST http://localhost:8080/lra-coordinator/start
http://localhost:8080/lra-coordinator/0_ffffc0a8000e_9471_60ed85da_a

Note the id of the new LRA in the output.

Now try closing the LRA (include the uid part of the LRA id followed by /close):

curl -XPUT http://localhost:8080/lra-coordinator/0_ffffc0a8000e_9471_60ed85da_a/close
Closed

You may verify that the coordinator no longer has a record of the LRA:

curl http://localhost:8080/lra-coordinator
[]

The output will be a json array ([]) of the LRA’s that the coordinator is managing. Check that the array does not contain the id of the LRA that you have just successfully closed.

Writing and running an LRA participant

We will generate and run a microservice that participates in an LRA using quarkus. A participant should be a JAX-RS resource so we will use the quarkus-maven-plugin, specifying the resteasy-jackson and rest-client extensions (the reason we need rest-client is that the narayana-lra participant support is implemented via a JAX-RS filter which will intercept business requests and needs to invoke the coordinator via JAX-RS calls):

cd ..
mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=narayana-lra-quickstart \
    -Dextensions="resteasy-jackson,rest-client"
cd narayana-lra-quickstart

There is an outstanding pull request for a narayana-lra quarkus extension (io.quarkus:quarkus-narayana-lra) which includes the necessary support for LRA. Since that isn’t available yet you need to manually do what the extension will do (which, fortunately, is neither difficult nor complex):

Include the following maven dependencies in the generated pom:

    <dependency>
      <groupId>org.eclipse.microprofile.lra</groupId>
      <artifactId>microprofile-lra-api</artifactId>
      <version>1.0</version>
    </dependency>
   <dependency>
      <groupId>org.jboss.narayana.rts</groupId>
      <artifactId>narayana-lra</artifactId>
      <version>5.12.0.Final</version>
    </dependency>

These two dependencies pull in support for the MicroProfile LRA annotations and the Narayana LRA implementation of the behaviour implied by these annotations.

We also need to tell quarkus (via the application.properties config file) to exclude some types from its CDI processing (these types are pulled in by the narayana dependency):

echo "quarkus.arc.exclude-types=io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry,io.narayana.lra.filter.ServerLRAFilter,io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantResource" >> src/main/resources/application.properties

And finally, we just need to update the generated Java JAX-RS resource source code to make use of Long Running Actions (which is the most interesting part for developers):

Open the file src/main/java/org/acme/GreetingResource.java in an editor and annotate the hello method with an @LRA annotation. In addition add two callback methods which will be called when the LRA is closed or cancelled.

// import annotation definitions
import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
import org.eclipse.microprofile.lra.annotation.Compensate;
import org.eclipse.microprofile.lra.annotation.Complete;
// import the definition of the LRA context header
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;

// import some JAX-RS types
import javax.ws.rs.PUT;
import javax.ws.rs.core.Response;
import javax.ws.rs.HeaderParam;
...

    // annotate the hello method so that it will run in an LRA:
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @LRA(LRA.Type.REQUIRED) // an LRA will be started before method execution and ended after method execution
    public String hello(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        return "Hello RESTEasy";
    }

    // ask to be notified if the LRA closes:
    @PUT // must be PUT
    @Path("/complete")
    @Complete
    public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        return Response.ok().build();
    }

    // ask to be notified if the LRA cancels:
    @PUT // must be PUT
    @Path("/compensate")
    @Compensate
    public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
        return Response.ok().build();
    }

Now build and start the application:

mvn clean package -DskipTests
java -Dquarkus.http.port=8081 -jar target/quarkus-app/quarkus-run.jar &

Ensure that the application and the coordinator are running on different ports, here I use 8081 for the application with the coordinator listening on port 8080 which is the default (I will show in a later blog how to change the default location of the coordinator).

Make a REST request to the hello method:

curl http://localhost:8081/hello

Just before the hello method is invoked an LRA will be started and the participant resource will be enlisted with the LRA. After the method finishes the LRA will be ended automatically (which is the default behaviour of the @LRA annotation). Ending the LRA triggers the termination phase in which the coordinator will invoke the @Complete method (called completeWork in the example) or the @Compensate method (called compensateWork in the example) of each enlisted participant depending on whether the LRA is closing or cancelling. If you want to verify that things are working as expected try updating the resource example to print the value of the HTTP header called Long-Running-Action (see the Java constant LRA_HTTP_CONTEXT_HEADER) which gets injected as a method parameter to each of the annotated methods. Alternatively run the participant in a debugger, for example if you break point inside the hello method and inspect the lraId method parameter and then compare it with what the coordinator knows (curl http://localhost:8080/lra-coordinator) then you should notice that the LRA is in the Active state. Then release the debugger and check back with the coordinator (the LRA will be gone since it should have completed). Note also that the lraId parameter should be the same as the one passed to the @Complete method so setting a break point in that method may also be illuminating.

Recap of what we’ve said before about compensations

12/2017 Narayana LRA: implementation of saga transactions

In this blog Ondra Chaloupka provided an overview of the Saga pattern and then identified those features of LRA that implement the pattern. His article also provided links to the Narayana code and quickstarts, and in particular introduced a worked example of how to run it in a cloud based environment using Minishift (OpenShift on your laptop).

12/2017 Saga implementations comparison

Another interesting article contributed by Ondra where he takes a different approach to explaining the concepts and mechanics of LRA’s. In this essay he compares and contrasts the Narayana LRA implementation with two popular Saga implementations: Axon framework and Eventuate.io. This approach is particularly useful for users already familiar with these other frameworks to get a rapid understanding of what LRA is offering.

11/2017 A comparison of Long Running Actions with a recent WSO paper

Tom Jenkinson proves "a high-level comparison of the approach taken by the LRA framework with a paper released to the 2017 IEEE 24th International Conference on Web Services - “WSO: Developer-Oriented Transactional Orchestration of Web-Services”." describing the various concepts introduced in both approaches: ordering compensations, idempotency, structure, ease of use, locking and orchestration and nesting of activities.

Tom describes LRA thus: "This specification is tailored to addressing needs of applications which are running in highly concurrent environments and have the need to ensure updates to multiple resources have atomic outcomes, but where locking of the resource manager has an unacceptable impact on the overall throughput of the system. LRA has been developed using a cloud first philosophy and achieves its goal by providing an extended transaction model based on Sagas. It provides a set of APIs and components designed to work well in typical microservice architectures."

06/2017 Sagas and how they differ from two-phase commit

Yet another article provided by Ondra, a busy year for him. Each of Ondra’s 2017 articles, though focused on communicating what LRA is and when, where and why it can be useful, do an excellent job at covering different facets of the compensation based approach to achieving distributed consistency. Ondra provides an extensive overview of the various concepts involved in these two transaction models [sagas and LRA’s].

Of particular interest is the extensive set of references provided at the end of the blog.

10/2016 Achieving Consistency in a Microservices Architecture

This article provides an overview of the problem domain that LRA addresses and draws attention to some of the difficulties that naive approaches run into when attempting their resolution. Although the article, written in 2016, predates the Narayana LRA implementation it does outline the basic LRA protocol.

07/2013 Compensating Transactions: When ACID is too much

An epic four part series contributed by Paul Robinson covering many aspects of the compensation based approach to transactions:

Part 2: Non-transactional Work. This part will cover situations where you need to coordinate multiple non-transactional resources, such as sending an email or invoking a third party service.

Part 3: Cross-domain Distributed Transactions: This part covers a scenario where the transaction is distributed, and potentially crosses multiple business domains.

Part 4: Long-lived Transactions. This part covers transactions that span long periods of time and shows how it’s possible to continue the transaction even if some work fails.

05/2015 XA and microservices, 05/2014 Transactions and Microservices and 04/2015 Microservices and transactions - an update

Three posts in which Mark allays a number of fears, concerns and fallacies that developers may have with combining transactions with microservices.

03/2011 Slightly alkaline transactions if you please …

Mark introduces his short post with: "Given that the traditional ACID transaction model is not appropriate for long running/loosely coupled interactions, let’s pose the question, “what type of model or protocol is appropriate?”", and then he goes on to answer the question he poses. Along the way we find definitions and links to papers that define various extensions to the traditional model giving us the "lay of land", so to speak, to enable us to navigate our way to an understanding of alternate models.

03/2011 When ACID is too strong

Another short post in which Mark presents the motivation for long-running activities.

03/2011 REST, Cloud and transactions

Mark motivates the case for REST based transaction protocols as a complement to WS-Transactions. LRA is such a REST based protocol and his post provides important background material on why LRA exists alongside WS-BA.

10/2011 nested transactions 101

Some useful background information on nested transactions (partially motivates nested LRA’s)


Wednesday, June 9, 2021

Narayana 5.12.0.Final released

This week we released Narayana 5.12.0.Final. You can download it from our website at https://narayana.io/downloads/index.html This release includes a mix of enhancements (including support for CDI 2.0) and bug fixes, full details can be found in the release notes.

Monday, March 22, 2021

Narayana 5.11.0.Final released

The team are pleased to announce our latest release of Narayana - the premier open source transaction manager.

The release is available for download from our website.

The release includes a mix of new features, LRA and slot store, plus a number of other smaller enhancements together with bug fixes. A full list of what we fixed is available in the release notes.

As always we generate comparative TPS figures for each release and this new release performs favourably against other open source transaction managers.

Wednesday, December 4, 2019

Narayana 5.10.1.Final released

The team are pleased to announce our latest release of Narayana - the premier open source transaction manager.

The release is available for download from our website.
It is a bug fix release and a list of what we fixed is available in the release notes.

We always generate comparative TPS figures for each release and this new release performs favourably against other open source transaction managers.

Friday, November 8, 2019

Narayana 5.10.0.Final released

The team are pleased to announce our latest release of Narayana - the premier open source transaction manager.

The release is available for download from our website.
It is a bug fix release and a list of what we fixed is available in the release notes.


Monday, October 7, 2019

Software Transactional Memory with Quarkus

We have recently contributed a quarkus extension called quarkus-narayana-stm which simplifies the use of STM in your microservices.

It will be available in the 0.24.0 quarkus release. If you would like to experiment with it before this release then you can either take one of the nightly builds or you can build it locally by git cloning the quarkus repo and then run the build. This will add the io.quarkus:quarkus-narayana-stm:999-SNAPSHOT maven dependency to your local maven repository and you may then get started by following the guide. There is also a quickstart that provides a worked example of how to use it in your microservices. The example shows how to manage concurrent accesses to a single counter. More sophisticated usage patterns are the norm but this simple example does give a flavour of how easy it is to manage concurrency with the Narayana STM implementation.

Wednesday, September 18, 2019

Heuristic exceptions

A transaction is finished either with commit or rollback. But have you considered that the third transaction outcome is <<unspecified>>? This third type of outcome may occur when the participant does not follow the coordinator's orders and makes a decision on the contrary. Then the outcome of the whole transaction could be inconsistent. Some participants could follow the coordinator guidelines to commit while the disobedient participant rolls-back. In such a case, the coordinator cannot report back to the application that "the work was successfully finished". From the perspective of the outside observer, the consistency is damaged. The coordinator itself cannot do much more. It followed the rules of the protocol but the participants disobeyed. Such transaction can only be marked with <<unspecified>> result — which is known as heuristic outcome.
The resolution of that outcome requires a third-party intervention. Saying differently somebody has to go and verify the state of data and make corrections.

XA specification

In scope of this article we talk about the two phase commit protocol and how the XA specification uses it.
XA specification takes the two-phase commit, the abstractly defined consensus protocol, and carries it down to the ground of implementation. It defines rules for communication amongst parties, prescribes state model and assesses outcomes, exceptions etc.
Where the 2PC talks about coordinator and participants the XA specification define a model where the coordinator is represented by a transaction manager (TM), the participant is modelled as the resource manager (RM) and the employer of them is an application program which talks to both of them.
The transaction manager drives a global transaction which enlists resource managers as participants responsible for part of work. For the work managed by RM is used term transaction branch. The resource manager is represented normally by a Java code that implements the JTA API while it uses some internal mechanism to call the underlying data resource. The resource manager could be represented by a JDBC driver and then underlying resource could be the PostgreSQL database running in some distinct datacenter.

JTA Specification

The Java Transaction API is a projection of XA specification to the language of Java. In general, it's a high-level API that strives to provide a comprehensible tool for handling transactional code based on the XA specification.

Several purposes of JTA

The JTA is meant to be used first by an application developer. In terms of XA specification, it would be the application program. Here we have the UserTransaction interface. It gives the chance to begin and commit the transaction. In terms of XA specification, it's a representation of the global transaction. In a newer version (from version 1.2) JTA defines the annotations like Transactional That gives the user chance to control the application scope declaratively. As you can notice neither with the UserTransaction nor with he @Transactional annotation you can't do much more than to define where the transaction begins and where it ends. How to put a participant into the transaction? That limited capability is because developer is anticipated running the application in a managed environment. It could be for example Java EE application server or a different container.

The container is a second consumer of the JTA API. JTA gives the entry point to the world of transaction management. The container uses the interface TransactionManager — which provides ability of managing transaction scope but also gives access to the Transaction object itself. Transaction is used to enlist the resource (the participant) to the global transaction. As the resource for enlistment is used the XAResource interface in JTA. The XAResource is managed by the transaction manager and is operated by the resource manager. Then container may arrange Synchronizations are callbacks called at the start and the end of the 2PC processing (used e.g. by JPA).

The third perspective where JTA participates in is communication with resource managers. The API defines the class XAResource (represents a participant resource to be enlisted to the global transaction), XAException (represents an error state defined in the XA specification) and Xid. The Xid represents an identifer which is unique for each transaction branch and consists of two parts — first an unique identifier of the global transaction and second an unique identifier of the resource manager. If you want to see how the transaction manager uses these classes to communicate with the resource manager take a look at the example from documentation of SQL Server.

Note:If you study the JTA API by looking into the javadoc and strolling the package summary then you can wonder about some other classes which were not mentioned here. Part of the javax.transaction package are interfaces used exclusively by JTS (transaction services running with ORB). That's mitigated by the fact the Java SE 11 removed support of ORB and those classes were removed from JDK as well.
Plus the JTA classses are now (from Java SE 11) split over Java SE and Java EE bundles as the package javax.transaction.xa is solely part of the Java SE while javax.transaction belongs to the Jakarta EE API.

Type of failures

Now when we have talked the model let's see the failure states. First, it's needed to realize that a failure in the application does not mean failure from the protocol perspective. If there is trouble in the application code or a network is transiently not available such occurrences can lead to rollback. It's a failure from the perspective of the application but for the transaction manager the rollback is just another valid state to shift to.
Even if the whole transaction manager crashes (e.g. the underlying JVM is killed) the system still should maintain data consistency and the transaction is recovered when the transaction manager (or rather recovery manager) comes back to life.

What's the trouble for the protocol is an unexpected behaviour of the resource manager (or the backed resource). We can track basically two incident types. A heuristic outcome where the resource deliberately decides to process some action (which is different from transaction manager decision). Or a heuristic outcome caused by a bug in the code (either of the transaction manager or the resource manager).

Let's discuss some examples for these types.
The deliberate decision could be a situation where the transaction manager calls the prepare on the database. The database confirms the prepare — it promises to finish the transaction branch with commit. But unfortunately the transaction manager crashes and nobody comes to restart it for a long time. The database decides that it's pretty long time to hold resources and delaying other transactions to proceed. Thus it decides to commit the work. The processing in the database may continue from that time. But later the transaction manager is restarted and it tries to commit all other branches belonging to the global transaction (let's say e.g. a JMS broker). That resource responds with an error and the transaction manager decides to rollback the whole global transaction. Now it accesses the database with the request for the rollback. But the database already committed its branch — the heuristic outcome just occurred.

An example for the bug in the code could be the PostgreSQL database driver. The driver was returning a wrong error code in case of intermittent connection failure. The XA specification defines that in such case the XAException should be thrown and it has to carry one of the following error codes — either the XAException.XAER_RMFAIL or the XAException.XA_RETRY. But the JDBC driver was returning XAException.XAER_RMERR. Such error code means that an irrecoverable error occurred. It makes the transaction manager think there is no way of automatic recovery and it switches the state of such transaction to heuristic immediately.

Heuristic exceptions

As the last part of this article we take a look on the heuristic outcomes of the transaction. The heuristics is represented with an exception being thrown. The exception reports the reason of the failure. It does so with error code or with type of class.

There are two main types of exception classes. First type is the XAException. This one is part of the communication contract between the transaction manager and the resource manager. It should not happen for the application code to obtain this type of exception. But for sure you can observe the XAException in the container log. It shows that there happened an error during transaction processing.

The second type is represented with multiple classes named Heuristic*Exception. These are exceptions that application code works with. They are thrown from the UserTransaction methods and they are checked.

Heuristic outcome with XAResource

The XAException reports reason of failure with the use of error codes. XA specification defines the meaning. It depends on the context in which it's used. For example the code XAException.XA_RETRY could be used for reporting error from commit with meaning to retry the commit action. But on the other hand it's not permitted to be used as an error code for the one-phase commit.

Then where are those heuristic states? Let's check what could happen when the transaction manager calls the XAResource calls of prepare, commit and rollback.
If the prepare is called then there is not many chances that heuristic occurs. At that time no promise from the resource is placed and the work can be easily roll-back or the worse timed-out. The only occurrence that can bring the system to the heuristic state is if the resource manager returns undefined code for this phase. But that is cause the most probably only by a bug in the implementation. Consult the XA specification which are those.
The more interesting are the commit and rollback calls. The commit and rollback are (or could be) called after the prepare. Heuristic exception means that the resource promised to commit (he acknowledge the prepare call) but it does not wait for transaction manager to command it for the next action and it finished the transaction branch on its own. The error codes are those with prefix XA_HEUR*. The decision on its own does not mean an error for the protocol in all cases.

Let's talk about rollback now. The global transaction was successfully prepared but the transaction manager decided at the end to roll-back it. It calls the rollback to the XAResource. The error XAException.XA_HEURRB announces that the resource manager decided to roll-back the transaction branch prior it was asked for it by the transaction manager. But as the transaction manager decided to go for the roll-back too the heuristic outcome followed the decision.
The XAException.XA_HEURCOM means that all work represented by the transaction branch was committed (at time the rollback is executed on the XAResource). That's bad from the data consistency as some other transaction branches could be already rolled-back.
To explain the meaning of the XAException.XA_HEURMIX it's needed to mention that the transaction branch could consist of several "local transactions". For example, PostgreSQL JDBC driver starts a database transaction to insert data to the database. Later (still in the scope of the same global transaction) it decides to update the data. It starts another database transaction. The transaction manager is clever enough to join those two database transactions which belong to the same database resource (controlled by the same resource manager) under the one transaction branch. It's good as it could reduce the communication overhead. So the XA_HEURMIX says that part of workload involved in the transaction branch was committed and the other part was rolled-back.
The XAException.XA_HEURHAZ says that the resource manager made a decision on its own but it's not capable to say what was the result of such an independent decision.

The most interesting part is the commit call. First it uses the XA_HEUR* exceptions in the same meaning as the rollback call and all what is said in the previous paragraph pays for the commit too. But up to that there are three new error codes. They do not contain word HEUR but in result they mean it. Those are XAER_RMERR which announces that an unspecified error happened during the currently executing commit call. But instead of committing the resource manager had just rolled-back the transaction branch. That means we are in the same state as with the XA_HEURRB The XAER_NOTA says that resource manager does not know anything about this transaction branch. That means the resource manager lost the notion about it and it either commits it or rolled-back it or it may do an arbitrary one in the future. That means we are in the same state as with the XA_HEURHAZ. The last one is the XAER_PROTO which says that the commit was called in a wrong context — for example it was called without the prepare being invoked before. This seems being similar to XAER_NOTA and thus have the same impact as the XA_HEURRB.

Heuristic outcome with the "application exceptions"

For the "application exceptions" it could be considered easier. The heuristic exceptions can be thrown only from the commit call (see UserTransaction javadoc). The UserTransaction gives chance to finish the transaction with commit or roll-back.

The roll-back means that transaction branches should be aborted and all work discarded. When UserTransaction.rollback() is called the resource manager had not promised succesful outcome yet. The time the rollback is called is time when all transaction processing data is available only in memory. Thus resource manager has no chance to decide differently from transaction manager. If there is some trouble then other types of exceptions are thrown — like IllegalStateException or SystemException (see the UserTransaction javadoc).

It's different with the UserTransaction.commit. This call means that two-phase commit protocol is to be started and XAResource.prepare/commit/rollback calls are involved. The JTA uses the checked exceptions to inform the application to handle the trouble. The application checked exceptions are RollbackException,HeuristicMixedException,HeuristicRollbackException.

  • The RollbackException is not a heuristic exception (at least by name) but still. That exception informs that even the code asked for commit all work (in all transaction branches) was undone by the rollback.
  • The HeuristicMixedException means that some transaction branches were committed and others were rolled-back. This is exception thrown for example if during the commit phase of 2PC. One of the XAResource.commit calls returns XAException.XA_HEURRB (aka it was rolled-back) while the others were succesfully committed.
  • The HeuristicRollbackException has the same final outcome from the global transaction perspective as the RollbackException. It only emphasizes that the fact that the roll-back was deliberately chosen by all the resources prior to the commit was executed by the transaction manager. In comparison, the RollbackException means that the transaction manager was just trying to commit all resources but during the process of committing trouble occurred and all the work was rolled-back (all resources rolled-back). To be perfectly honest I'm not sure I can't see a real difference between these two.

As we've just talked about all exceptions defined at the UserTransaction.commit definition so we are done, right?
Oh wait, we are not!

There is one more exception defined in the javax.transaction package. It's the HeuristicCommitException. The HeuristicCommitException is not defined at the UserTransaction.commit as even all resources would idependently decide to commit the global transaction result is still just committed. Which is intended as UserTransaction.commit is called. Then what is the purpose of it then?
We need to look into the implementation. It's used at calls of commit and rollback at a subordinate transaction. The subordinate transaction is a transaction which is driven by a parent transaction. The parent transaction (named as top-level as well) manages the subordinate and decides the overall outcome.
When the subordinate transaction is commanded it reports the outcome back to the top-level one. It's a similar relation as the XAResource has to the global transaction. Because the subordinate transaction needs to report heuristic decisions back from the commit and rollback calls the HeuristicCommitException serves for cases when subordinate transaction decided to commit prior the top-level transaction commanded for a final action.

NOTE: Don't interchange the subordinate transaction for the nested transaction. If the nested transaction is aborted the upper transaction can continue in processing and it can finish with commit at the end (but if the top-level transaction rolls-back the nested transaction has to roll-back as well).
The subordinate transaction is a composite part of the top-level transaction. If the subordinate transaction aborts the top-level one aborts as well.

Summary

That's all. Hopefully, you understand a bit more on the meaning of the heuristic outcomes for the XA and JTA specifications. And for sure you won't be writing code like

try {
  UserTransaction.begin();
  ...
  UserTransaction.commit();
} catch (Throwable t) {
  // some strange error happened so we print it to the log
  t.printStackTrace();
}