# Java SDK

Send invoices to Complyance from a Java application: a sample payload for mapping, then real invoices from the same source, their status and your purchase invoices. You need Java 11 or later, Maven or Gradle, and a sandbox API key.

> **Before you start**
>
> - Java 11 or later.
> - A Complyance workspace with a source registered when you [onboarded your company](https://docs.complyance.io/one-platform/invoice-portal/onboard/). You need its name and version.
> - A sandbox API key from the [Developer portal](https://docs.complyance.io/one-platform/developer-portal/api-keys/). See [Authentication and environments](https://docs.complyance.io/api-reference/authentication/) for how keys and environments match.
> - Work in sandbox until your invoices pass. Switch to production last.

## Install

**Maven**

```xml title="pom.xml"
<dependency>
  <groupId>io.complyance</groupId>
  <artifactId>unify-sdk</artifactId>
  <version>3.0.23-beta</version>
</dependency>
```

**Gradle**

```groovy title="build.gradle"
implementation 'io.complyance:unify-sdk:3.0.23-beta'
```

The package is on Maven Central. `3.0.23-beta` is the latest version, and it is a beta release. Some older guides name the artifact `io.complyance:sdk`. That artifact does not exist; use `io.complyance:unify-sdk`.

## Configure the SDK

Put the configuration in one class and call `Complyance.configure()` once, when your application starts. The examples on this page call it at the start of their `main` method.

```java title="Complyance.java"
import io.complyance.sdk.Environment;
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.SDKConfig;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class Complyance {
    // The source registered for your workspace
    static final String SOURCE_NAME = "acme-erp";
    static final String SOURCE_VERSION = "1.0";

    static void configure() {
        String apiKey = System.getenv("COMPLYANCE_API_KEY");
        if (apiKey == null || apiKey.isEmpty()) {
            throw new IllegalStateException("Set COMPLYANCE_API_KEY first.");
        }

        SDKConfig config = new SDKConfig(
                apiKey,
                Environment.SANDBOX,
                Collections.emptyList());
        GETSUnifySDK.configure(config);
    }

    // Your own invoice data, in your system's format
    static Map<String, Object> samplePayload() {
        return Map.of(
                "invoice_data", Map.of(
                        "document_number", "INV-2026-0142",
                        "invoice_date", "2026-09-22",
                        "currency_code", "AED",
                        "total_amount", 5250.00),
                "seller_info", Map.of(
                        "seller_name", "Acme Trading LLC",
                        "vat_number", "100000000000003"),
                "buyer_info", Map.of(
                        "buyer_name", "Globex Distribution LLC"),
                "line_items", List.of(Map.of(
                        "item_name", "Office chair",
                        "quantity", 10,
                        "unit_price", 500.00,
                        "tax_rate", 5.0)));
    }
}
```

Change these parts:

- `COMPLYANCE_API_KEY`: the environment variable that holds your key. Load it from your secret manager. Never write the key in code.
- `Environment.SANDBOX`: use `Environment.PRODUCTION` with a production key when you go live. The key’s environment must match.
- `SOURCE_NAME` and `SOURCE_VERSION`: your source’s name and version, exactly as registered. You pass them with every request.
- `samplePayload()`: replace it with an invoice from your own system.

### Retries

The SDK tries a request up to five times when the network fails or the API answers `429` or a server error, such as `500` or `503`. To change this, pass a retry configuration as the fourth argument of `SDKConfig` in `Complyance.configure()`, and import `io.complyance.sdk.RetryConfig`:

```java title="Choose a retry configuration"
SDKConfig config = new SDKConfig(
        apiKey,
        Environment.SANDBOX,
        Collections.emptyList(),
        RetryConfig.conservativeRetry());
```

The presets are `RetryConfig.defaultConfig()`, `RetryConfig.conservativeRetry()`, `RetryConfig.aggressiveRetry()` and `RetryConfig.noRetry()`, which sends each request once.

## Send a sample payload for mapping

Send one invoice from your system as it is, with the purpose `Purpose.MAPPING`. Complyance stores the payload so you can map its fields to GETS, the Complyance standard invoice format, in the Integration engine. It is not validated or sent to a tax authority. The purposes are described in [Submit and retrieve](https://docs.complyance.io/api-reference/unify/submit-and-retrieve/#purpose).

```java title="SendSamplePayload.java"
import io.complyance.sdk.BASE;
import io.complyance.sdk.Country;
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.GetsDocumentType;
import io.complyance.sdk.Mode;
import io.complyance.sdk.Operation;
import io.complyance.sdk.Purpose;
import io.complyance.sdk.SDKException;
import io.complyance.sdk.UnifyResponse;

public class SendSamplePayload {
    public static void main(String[] args) throws SDKException {
        Complyance.configure();

        GetsDocumentType documentType = GetsDocumentType.builder()
                .base(BASE.TAX_INVOICE)
                .build();

        UnifyResponse response = GETSUnifySDK.pushToUnify(
                Complyance.SOURCE_NAME,
                Complyance.SOURCE_VERSION,
                documentType,
                Country.AE,
                Operation.SINGLE,
                Mode.DOCUMENTS,
                Purpose.MAPPING,
                Complyance.samplePayload(),
                true);

        System.out.println(response.getMessage());
    }
}
```

The last argument, `true`, sends the `new-api: true` header, so the request and the response use the current format described in [Submit and retrieve](https://docs.complyance.io/api-reference/unify/submit-and-retrieve/). Versions `3.0.18-beta` and later accept it. Keep it on every call on this page.

You should see:

```
Payload saved for mapping.
```

Then build the mapping in the portal:

1. [Create an integration](https://docs.complyance.io/one-platform/developer-portal/integration-engine/create/) for the same country and document type. At **Payload Selection**, choose the payload you sent.
2. [Map your fields to GETS](https://docs.complyance.io/one-platform/developer-portal/integration-engine/gets-mapping/), then run the [testbed](https://docs.complyance.io/one-platform/developer-portal/integration-engine/test/) until it passes.
3. [Link your source](https://docs.complyance.io/one-platform/developer-portal/source-management/) to the integration in **Source management**.

## Submit an invoice

Once your source is linked to a tested integration, send invoices with the purpose `Purpose.INVOICING`. Use the same source name and version, so Complyance converts the payload with your mapping.

```java title="SubmitInvoice.java"
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.complyance.sdk.BASE;
import io.complyance.sdk.Country;
import io.complyance.sdk.ErrorCode;
import io.complyance.sdk.ErrorDetail;
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.GetsDocumentType;
import io.complyance.sdk.Mode;
import io.complyance.sdk.Operation;
import io.complyance.sdk.Purpose;
import io.complyance.sdk.SDKException;
import io.complyance.sdk.UnifyResponse;

public class SubmitInvoice {
    public static void main(String[] args) throws Exception {
        Complyance.configure();

        GetsDocumentType documentType = GetsDocumentType.builder()
                .base(BASE.TAX_INVOICE)
                .build();

        try {
            UnifyResponse response = GETSUnifySDK.pushToUnify(
                    Complyance.SOURCE_NAME,
                    Complyance.SOURCE_VERSION,
                    documentType,
                    Country.AE,
                    Operation.SINGLE,
                    Mode.DOCUMENTS,
                    Purpose.INVOICING,
                    Complyance.samplePayload(),
                    true);

            if ("queued".equals(response.getStatus())) {
                // The retries ran out; the SDK saved the request to send again later
                System.err.println(response.getMessage());
            } else {
                System.out.println("Document ID: " + response.getDocumentId());
            }
        } catch (SDKException e) {
            ErrorDetail detail = e.getErrorDetail();
            if (detail == null) {
                throw e;
            }
            if (detail.getCode() == ErrorCode.VALIDATION_FAILED) {
                // The invoice failed validation: the response body lists every problem
                JsonNode body = new ObjectMapper().readTree((String) detail.getContextValue("responseBody"));
                for (JsonNode error : body.path("errors")) {
                    System.err.println(error.path("code").asText() + " " + error.path("getsPath").asText()
                            + ": " + error.path("message").asText());
                }
            } else {
                // The request was refused, for example an unknown source
                System.err.println(detail.getCode() + ": " + e.getMessage());
                System.err.println(detail.getSuggestion());
            }
        }
    }
}
```

`ObjectMapper` comes from Jackson, which the SDK already depends on.

The document type is a GETS base, such as `BASE.TAX_INVOICE` or `BASE.CREDIT_NOTE`, plus any modifiers that apply, such as `.modifiers(MODIFIER.EXPORT)` or `.modifiers(MODIFIER.CONTINUOUS_SUPPLY)`. It decides which fields the invoice needs. The SDK refuses a base or modifier that the country does not use before it sends anything. For the UAE it accepts the bases `tax_invoice` and `credit_note`, and every UAE modifier except `reverse_charge`; the full list is in [Base and modifiers](https://docs.complyance.io/api-reference/gets/base-and-modifiers/).

In sandbox and production the SDK sends invoices for `Country.SA`, `Country.MY`, `Country.AE`, `Country.BE` and `Country.DE`. It has no country value for Oman.

A validated invoice returns a `documentId`. Keep it to check the status later.

```json title="Invoice validated"
{
  "documentId": "01K5EXAMPLE00000000000142",
  "documentNumber": "INV-2026-0142",
  "message": "Your invoice was validated and is ready for submission.",
  "Base64XML": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…"
}
```

`getDocumentId()`, `getMessage()` and `getBase64XML()` return these values. `Base64XML` is the e-invoice XML, Base64-encoded; it is empty for countries where it is not produced at this point.

An invoice that fails validation comes back as HTTP `422`. The SDK throws `SDKException` with the code `VALIDATION_FAILED`, and keeps the response body in the error detail as `responseBody`. Its `errors` list every problem, each with a `code`, such as `IBR-015`, the GETS field in `getsPath`, the field in your payload in `payloadPath`, and a `message`. Fix every entry before you send the invoice again. If the code is `GETS-HEAD-010`, the document number was already used: change it.

Send your real test cases in sandbox and run them in the [testbed](https://docs.complyance.io/one-platform/developer-portal/testbeds/) before you switch to production.

## Check a document’s status

Delivery to the tax authority or the Peppol network continues after the response. Look the document up by its `documentId`:

```java title="CheckStatus.java"
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.SDKException;
import java.util.Map;

public class CheckStatus {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws SDKException {
        Complyance.configure();

        Map<String, Object> result = GETSUnifySDK.getSalesInvoice("01K5EXAMPLE00000000000142");
        Map<String, Object> document = (Map<String, Object>) result.get("data");

        System.out.println("State: " + document.get("state"));
        System.out.println("Finished: " + document.get("isTerminal"));
        System.out.println("Tax authority: " + document.get("government"));
    }
}
```

```
State: final_valid
Finished: true
Tax authority: {status=APPROVED}
```

Check again until `isTerminal` is `true`. Every state and field is described in the [documents reference](https://docs.complyance.io/api-reference/unify/documents/). Checking the status is how you learn whether the invoice was accepted.

## Retrieve purchase invoices

Purchase invoices are the ones your suppliers send you. Your API key decides the workspace and environment they come from.

Get one by its `documentId`, from a listing or a [purchase webhook](https://docs.complyance.io/one-platform/developer-portal/webhooks/purchase-invoices/):

```java title="GetPurchaseInvoice.java"
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.SDKException;
import java.util.Map;

public class GetPurchaseInvoice {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws SDKException {
        Complyance.configure();

        Map<String, Object> result = GETSUnifySDK.getPurchaseInvoice("01K5EXAMPLE00000000000142");
        Map<String, Object> invoice = (Map<String, Object>) result.get("data");

        System.out.println(invoice.get("documentNumber") + " " + invoice.get("state"));
    }
}
```

List the invoices received in a date range, one page at a time:

```java title="ListPurchaseInvoices.java"
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.SDKException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ListPurchaseInvoices {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws SDKException {
        Complyance.configure();

        Map<String, String> filters = new HashMap<>();
        filters.put("from", "2026-09-01");
        filters.put("to", "2026-09-30");
        filters.put("limit", "100");

        while (true) {
            Map<String, Object> page = GETSUnifySDK.listPurchaseInvoices(filters);
            Map<String, Object> data = (Map<String, Object>) page.get("data");

            for (Object item : (List<?>) data.get("items")) {
                Map<String, Object> invoice = (Map<String, Object>) item;
                System.out.println(invoice.get("invoiceNumber") + " from " + invoice.get("supplierName"));
            }

            if (!Boolean.TRUE.equals(data.get("hasMore"))) {
                break;
            }
            filters.put("cursor", (String) data.get("nextCursor"));
        }
    }
}
```

Keep the same range and limit while you page. The fields and the date rules are in [Get a purchase invoice](https://docs.complyance.io/api-reference/purchases/get/) and [List purchase invoices](https://docs.complyance.io/api-reference/purchases/list/).

## Handle errors

A refused request throws `SDKException`. `SubmitInvoice.java` in [Submit an invoice](https://docs.complyance.io/sdks/java/#submit-an-invoice) prints its code, message and suggestion from `getErrorDetail()`.

The errors you are most likely to meet:

- **`AUTHENTICATION_FAILED`**: the API key is missing, invalid, revoked or expired. Check that `COMPLYANCE_API_KEY` is set in the environment your application runs in, and that the key is still active.
- **`AUTHORIZATION_DENIED`**: the key belongs to a different environment. Use a sandbox key with `Environment.SANDBOX` and a production key with `Environment.PRODUCTION`.
- **`INVALID_ARGUMENT`** with the message `Invalid GETS V2 documentType`: the base or a modifier is not used in that country. Fix the document type and send again.
- **`VALIDATION_FAILED`**: the invoice failed validation. Read the `errors` in the response body, as shown in [Submit an invoice](https://docs.complyance.io/sdks/java/#submit-an-invoice).

The status and purchase invoice methods throw `SDKException` with the code `API_ERROR` for any refused request, and the message names the HTTP status. What each status means is in [Errors](https://docs.complyance.io/api-reference/unify/errors/).

> **Next**
>
> Check which GETS fields your country and document type need in the [field reference](https://docs.complyance.io/api-reference/gets/fields/).

Source: https://docs.complyance.io/sdks/java/
