Library and reference POS for Java.

Maven Repository

The sections below explain how to use this library in your project, depending on what build tool you use.

Library version is henceforth referred to as 'X.Y.Z'. To find out what release versions are available, check the tags.


The easiest way to import the library into your project is using the Maven repository:

  1. Add Maven Central repository
  2. Import dependency into your project:
    • GroupId: `io.mx51'
    • ArtifactId: spi-client-java
    • Version: X.Y.Z

See specific examples for build tools below.

Gradle example

Declare the repository:

repositories {

And use the library as a dependency:

dependencies {
    compile 'io.mx51:spi-client-java:X.Y.Z'

Maven example

Configure the repository to be used for dependency resolution:


And use the library as a dependency:


Reference POS

Reference implementation is available in spi-samples-java.

Unlimited strength cryptography

The Java virtual machine (JVM), on any machine used to run this application must allow unrestricted cryptographic key sizes.

When running Java 8 Update 161 or higher, you don't need to do anything - the restriction should already be removed.

For earlier versions, follow these steps:

  1. Download 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files' (available from downloads).
  2. Uncompress and extract the downloaded file.
  3. Place the uncompressed files into the<JAVA_HOME>/lib/security directory for your JDK installation.

For more information and issues with specific versions, follow this explanation.

Importing and running

The easiest way to run the project is by importing it into IntelliJ IDEA. Follow the import wizard, choose "Import project from external model" and select "Gradle". When asked about the Gradle distribution, we recommend you pick the default wrapper. Any recent version of Gradle, however, should be sufficient.

Once imported, you can run the project by executing the Pos class.

Alternatively, you can run it from the command line using the Gradle wrapper as follows:

./gradlew --console plain run -q -PrunArgs='JAVABAR'

To make typing input cleaner, the above command compiles the application and runs it in plain console mode.


Sample code contains a lot of supporting code, which can make it hard to understand how everything works. The central points, however, are reasonably simple. Here is a step-by-step walk-through to help you understand the code.

1. Instantiate client

First, you need to instantiate the Spi client object with the inputs:

  • posId - uppercase alphanumeric string that identifies your POS instance
  • eftposAddress - the IP address of the target EFTPOS
  • spiSecrets - the pairing secrets, if you know it already, or null otherwise

Note that only the first two are required, as they’re used to connect to the EFTPOS. Secrets are only required if you previously paired with the terminal and will prevent you from having to pair again.

try {
    spi = new Spi(posId, eftposAddress, spiSecrets);
} catch (Spi.CompatibilityException e) {
    // Print and/or log exception, as needed

You’ll notice that the Spi constructor throws a CompatibilityException, which only happens when your setup is not compatible with SPI. If you encounter it, make sure you’ve followed all the setup steps on this page, including the Unlimited strength cryptography section.

2. Register event handlers

The next step is registering the handlers for any events being raised by the client.

// Called when pairing status changes.
spi.setStatusChangedHandler(new Spi.EventHandler<SpiStatus>() {
    public void onEvent(SpiStatus value) {
        // Handle value change
// Called when secrets are set, changed or voided.
spi.setSecretsChangedHandler(new Spi.EventHandler<Secrets>() {
    public void onEvent(Secrets value) {
        // Handle value change
// Called throughout to pairing process to update us with progress.
spi.setPairingFlowStateChangedHandler(new Spi.EventHandler<PairingFlowState>() {
    public void onEvent(PairingFlowState value) {
        // Handle value change
// Called throughout to transaction process to update us with progress.
spi.setTxFlowStateChangedHandler(new Spi.EventHandler<TransactionFlowState>() {
    public void onEvent(TransactionFlowState value) {
        // Handle value change

You must implement all of them, because that's how the library communicates its changes asynchronously back to your application.

Each handler is essentially a lambda, but since the library is compatible with JDK 6 and above, using actual lambdas (available in JDK 8+) is not possible.

3. Start

Now that everything is set up and you’re ready to connect to the terminal, you need to call the start() method.


This call is asynchronous, so it has no return value. Instead, any changes in state will be delivered via the event handlers you registered before.

4. Dispose

Your application needs to clean up the state by calling dispose() before it finishes. This kills any background threads and resets the Spi instance to its original state.


Note that dispose() is the reciprocal of start(), so it only cleans up the state produced by that, such as the connection and its status. Event handlers, IP address of the terminal, and the secrets will be kept and you can reuse the instance.


Note that only the information intended for the user interface will be written to the command line. Everything else will be logged using Log4j. To subscribe to that stream, your project needs to include a log4j2.xml configuration file in your resources.

The configuration used in this sample project (see src/main/resources/log4j2.xml) outputs the logs to a file called output.log in the root of the repository.