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.22-beta</version>
</dependency>

The package is on Maven Central. 3.0.22-beta is a prerelease, and it is the latest published version.

Configure the SDK

Configure the SDK once, when your application starts.

Configure the SDKjava
import io.complyance.sdk.Environment;
import io.complyance.sdk.GETSUnifySDK;
import io.complyance.sdk.SDKConfig;
import java.util.Collections;

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);
    }
}

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.

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. Nothing is sent to a tax authority.

Send a sample payloadjava
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.UnifyResponse;
import java.util.List;
import java.util.Map;

// Your own invoice data, in your system's format
Map<String, Object> payload = 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)));

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,
        payload,
        true);

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

The last argument, true, selects the request format described in Submit and retrieve. 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.

Submit an invoicejava
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.INVOICING,
        payload,
        true);

if (response.getError() != null) {
    // The request was refused, for example an unknown source
    System.err.println(response.getCode() + ": " + response.getMessage());
} else if (response.getErrors() != null && !response.getErrors().isEmpty()) {
    // The invoice failed validation
    for (Map<String, Object> error : response.getErrors()) {
        System.err.println(error.get("code") + " " + error.get("getsPath") + ": " + error.get("message"));
    }
} else if ("queued".equals(response.getStatus())) {
    // The service could not be reached; the SDK saved the request to send again later
    System.err.println(response.getMessage());
} else {
    System.out.println("Document ID: " + response.getDocumentId());
}

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.

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.

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:

Check the statusjava
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 field of the response is in Get a document. To be told instead of asking, use webhooks.

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:

Get a purchase invoicejava
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:

List purchase invoicesjava
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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. Read the reason from getErrorDetail():

Handle errorsjava
import io.complyance.sdk.SDKException;

try {
    UnifyResponse response = GETSUnifySDK.pushToUnify(
            Complyance.SOURCE_NAME, Complyance.SOURCE_VERSION, documentType, Country.AE,
            Operation.SINGLE, Mode.DOCUMENTS, Purpose.INVOICING, payload, true);
} catch (SDKException e) {
    if (e.getErrorDetail() != null) {
        System.err.println(e.getErrorDetail().getCode() + ": " + e.getMessage());
        System.err.println(e.getErrorDetail().getSuggestion());
    }
}

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.

A failed validation does not throw. The invoice comes back with getErrors(), one entry for each problem, as in Submit an invoice. Each entry has 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.

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.

Last updated