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.

Install

pom.xmlxml
<dependency>
  <groupId>io.complyance</groupId>
  <artifactId>unify-sdk</artifactId>
  <version>3.0.23-beta</version>
</dependency>

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.

Complyance.javajava
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:

Choose a retry configurationjava
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.

SendSamplePayload.javajava
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. Versions 3.0.18-beta and later accept it. Keep it on every call on this page.

You should see:

Output
Payload saved for mapping.

Then build the mapping in the portal:

  1. Create an integration for the same country and document type. At Payload Selection, choose the payload you sent.
  2. Map your fields to GETS, then run the testbed until it passes.
  3. Link your source 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.

SubmitInvoice.javajava
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.

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.

Invoice validatedjson
{
  "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 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:

CheckStatus.javajava
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"));
    }
}
Output
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. 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:

GetPurchaseInvoice.javajava
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:

ListPurchaseInvoices.javajava
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 and List purchase invoices.

Handle errors

A refused request throws SDKException. SubmitInvoice.java in 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.

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.