Electronic invoicing with Java
This tutorial builds three simple Java applications from scratch:
- Receive: connects and authenticates with the Invoicetronic API and downloads any new incoming invoices.
- Send: connects and authenticates with the Invoicetronic API and sends an invoice to the SDI.
- Update: connects and authenticates with the Invoicetronic API and consults the history of notifications returned by the SDI.
Before continuing, make sure all the prerequisites below are met.
Prerequisites
We assume that these prerequisites are met:
- Java 8+ (JDK) has been downloaded and installed
- Maven 3.6+ has been installed
- You obtained an active API Key
- You registered with the Italian Revenue Service (needed for the live environment)
We use Maven for dependency management, which is the de facto standard for modern Java projects.
Tip
For an optimal Java experience, consider using IntelliJ IDEA or Eclipse as your IDE.
Did you know?
The Java SDK is compatible with Spring Boot, Jakarta EE, and all modern enterprise Java applications.
Receive
Create the app
The first step is to create the application directory:
Configure Maven
Create a pom.xml file with the following configuration:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.invoicetronic.example</groupId>
<artifactId>receive-example</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.invoicetronic</groupId>
<artifactId>java-sdk</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
</project>
Configure the SDK
Create the directory structure and main file:
Create the file src/main/java/com/invoicetronic/example/Main.java:
package com.invoicetronic.example;
import com.invoicetronic.sdk.ApiClient;
import com.invoicetronic.sdk.ApiException;
import com.invoicetronic.sdk.Configuration;
import com.invoicetronic.sdk.auth.HttpBasicAuth;
import com.invoicetronic.sdk.api.ReceiveApi;
import com.invoicetronic.sdk.model.Receive;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Configure the SDK
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://api.invoicetronic.com/v1");
HttpBasicAuth basicAuth = (HttpBasicAuth) defaultClient.getAuthentication("Basic");
basicAuth.setUsername("YOUR TEST API KEY (starts with ik_test_)");
basicAuth.setPassword("");
}
}
As you can see, we configure the SDK by setting the base path and HTTP Basic authentication with your test API Key (not the live one). Notice how we use setUsername() for the API Key and setPassword("") empty.
API Key comes in pairs
When you create your account, you obtain a pair of API Keys. One is the test key for the API Sandbox, and the other is the live API's. You can tell the difference because the former starts with ik_test_, while the latter begins with ik_live_. In this tutorial, always use the test key.
Download invoices
We are ready to make a request. We want to download new vendor invoices that may be available from the SDI. Add this code in the main method:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
// Download unread invoices
ReceiveApi receiveApi = new ReceiveApi(defaultClient);
try {
List<Receive> inboundInvoices = receiveApi.receiveGet(
null, // companyId
null, // identifier
true, // unread
null, // committente
null, // prestatore
null, // fileName
null, // lastUpdateFrom
null, // lastUpdateTo
null, // dateSentFrom
null, // dateSentTo
null, // documentDateFrom
null, // documentDateTo
null, // documentNumber
true, // includePayload
null, // page
null, // pageSize
null // sort
);
System.out.println("Received " + inboundInvoices.size() + " invoices");
for (Receive invoice : inboundInvoices) {
if (invoice.getEncoding() == Receive.EncodingEnum.XML) {
try (FileOutputStream fos = new FileOutputStream(invoice.getFileName())) {
fos.write(invoice.getPayload().getBytes(StandardCharsets.UTF_8));
}
} else if (invoice.getEncoding() == Receive.EncodingEnum.BASE64) {
try (FileOutputStream fos = new FileOutputStream(invoice.getFileName())) {
fos.write(Base64.getDecoder().decode(invoice.getPayload()));
}
}
System.out.println("Downloaded " + invoice.getFileName() +
" from a vendor with VAT ID " + invoice.getPrestatore());
}
} catch (ApiException | IOException e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
Payload Inclusion
We set includePayload to true to retrieve the actual invoice content in the payload property. Without this parameter, the payload field would be null by default, which increases performance and reduces response size when you only need metadata.
Compile and run the application:
You should obtain an output similar to this one:
Received 3 invoices
Downloaded file1.xml from a vendor with VAT ID IT06157670966
Downloaded file2.xml.p7m from a vendor with VAT ID IT01280270057
Downloaded file3.xml.p7m from a vendor with VAT ID IT01280270057
The files are in the current directory, ready for you to inspect them.
Not receiving invoices in the live environment?
Ensure you registered with the Italian Revenue Service, which is a requirement for the live environment.
What we learned
In this example, we learned several things.
-
We must configure the SDK by getting the default client with
Configuration.getDefaultApiClient(), setting the base path, and configuring HTTP Basic authentication with username (API key) and empty password. -
We must instantiate a class representing the endpoint we want to work with. In this case, we leverage
ReceiveApito download incoming invoices, passing the configured client. -
Endpoint classes like
ReceiveApioffer methods for interacting with their target entity. We callreceiveGet()to retrieve invoices. Because we only want new, unread invoices, we passtruefor theunreadparameter. We also passtrueforincludePayloadto retrieve the actual invoice content. -
Invoice objects expose methods like
getEncoding(),getFileName(), andgetPayload(). The last one contains the invoice content, as plain text or Base64-encoded, as described bygetEncoding()which returns aReceive.EncodingEnum.
Source Code on GitHub
The source code for this Quickstart is also available on GitHub.
Send
Create the app
The first step is to create the application directory:
Configure Maven
Create a pom.xml file with the following configuration:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.invoicetronic.example</groupId>
<artifactId>send-example</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.invoicetronic</groupId>
<artifactId>java-sdk</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
</project>
Configure the SDK
Create the directory structure and main file:
Create the file src/main/java/com/invoicetronic/example/Main.java:
package com.invoicetronic.example;
import com.invoicetronic.sdk.ApiClient;
import com.invoicetronic.sdk.ApiException;
import com.invoicetronic.sdk.Configuration;
import com.invoicetronic.sdk.auth.HttpBasicAuth;
import com.invoicetronic.sdk.api.SendApi;
import com.invoicetronic.sdk.model.Send;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// Configure the SDK
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://api.invoicetronic.com/v1");
HttpBasicAuth basicAuth = (HttpBasicAuth) defaultClient.getAuthentication("Basic");
basicAuth.setUsername("YOUR TEST API KEY (starts with ik_test_)");
basicAuth.setPassword("");
}
}
As you can see, we configure the SDK by setting the base path and HTTP Basic authentication with your test API Key (not the live one).
API Key comes in pairs
When you create your account, you obtain a pair of API Keys. One is the test key for the API Sandbox, and the other is the live API's. You can tell the difference because the former starts with ik_test_, while the latter begins with ik_live_. In this tutorial, always use the test key.
Send an invoice
We are ready to make a request. We want to send an invoice to the SDI. Add this code in the main method:
// Send an invoice
String filePath = "/some/file/path/filename.xml";
Map<String, String> metaData = new HashMap<>();
metaData.put("internal_id", "123");
metaData.put("created_with", "myapp");
metaData.put("some_other_custom_data", "value");
SendApi sendApi = new SendApi(defaultClient);
try {
String payload = new String(Files.readAllBytes(Paths.get(filePath)));
Send sendData = new Send();
sendData.setFileName(Paths.get(filePath).getFileName().toString());
sendData.setPayload(payload);
sendData.setMetaData(metaData);
Send sentInvoice = sendApi.sendPost(sendData, null, null);
System.out.println("The invoice was sent successfully, it now has the unique Id of " +
sentInvoice.getId() + ".");
} catch (ApiException | IOException e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
Compile and run the application:
You should obtain an output similar to this one:
Check the invoice state
When you forward an invoice to the SDI, delivery is not instantaneous: the SDI runs a series of checks and returns a sequence of notifications that describe the state of the process (Inviato, Consegnato, Scartato, etc.). The Send model exposes a getLatestState() method with the current state, sparing you a separate /update call when you only need to know how it went.
// Fetch the most recent state of an already-sent invoice
Send fresh = sendApi.sendIdGet(sentInvoice.getId(), null);
String state = fresh.getLatestState() != null ? fresh.getLatestState().getValue() : "Processing";
System.out.println("Current state: " + state);
Right after submission, getLatestState() may return null: the SDI has not processed the document yet. Check again after a few seconds or, better, configure a webhook to receive a push notification on every state change.
Save API calls
Use getLatestState() on Send whenever you only need the current state: a single call instead of one to /send plus one to /update. Reach for UpdateApi only when you need the full transition history.
What we learned
In this example, we learned several things.
-
We must configure the SDK by getting the default client with
Configuration.getDefaultApiClient(), setting the base path, and configuring HTTP Basic authentication. -
We must instantiate a class representing the endpoint we want to work with. In this case, we leverage
SendApito send invoices. Endpoint classes likeSendApioffer methods for interacting with their target entity. We callsendPost()to send an invoice. -
The
Sendmodel exposes methods likesetFileName(),setMetaData(), andsetPayload(). The last one contains the invoice content, whilesetMetaData()is optional and binds custom data to the document. -
The
Sendmodel also exposesgetLatestState()with the current SDI state, readable viasendIdGet(id, null). It saves a/updatecall when you only need to know the state.
Source Code on GitHub
The source code for this Quickstart is also available on GitHub.
Update
For the current state of a sent invoice, just read getLatestState() from the Send model (see Check the invoice state). If instead you need the full transition history — for example to understand why an invoice was rejected, render every state transition with timestamps in your UI, or track the notifications returned by a public administration entity — use UpdateApi.
/update queries are free of charge
Requests to /update are not counted against your plan: you can poll the notification history as often as you need.
Create the application
Configure Maven
Create a pom.xml similar to the previous ones, replacing artifactId with update-example.
Retrieve the notification history
Create the file src/main/java/com/invoicetronic/example/Main.java:
package com.invoicetronic.example;
import com.invoicetronic.sdk.ApiClient;
import com.invoicetronic.sdk.ApiException;
import com.invoicetronic.sdk.Configuration;
import com.invoicetronic.sdk.auth.HttpBasicAuth;
import com.invoicetronic.sdk.api.UpdateApi;
import com.invoicetronic.sdk.model.Update;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Configure the SDK
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://api.invoicetronic.com/v1");
HttpBasicAuth basicAuth = (HttpBasicAuth) defaultClient.getAuthentication("Basic");
basicAuth.setUsername("YOUR TEST API KEY (starts with ik_test_)");
basicAuth.setPassword("");
// Id of the sent invoice we want to inspect
Integer sendId = 225;
UpdateApi updateApi = new UpdateApi(defaultClient);
try {
List<Update> updates = updateApi.updateGet(
null, // companyId
null, // identifier
null, // prestatore
null, // unread
sendId, // sendId
null, // state
null, // lastUpdateFrom
null, // lastUpdateTo
null, // dateSentFrom
null, // dateSentTo
null, // page
null, // pageSize
"last_update" // sort
);
System.out.println("Found " + updates.size() +
" notifications for invoice " + sendId);
for (Update update : updates) {
String description = update.getDescription() != null
? update.getDescription() : "OK";
System.out.println(" [" + update.getLastUpdate() + "] state=" +
update.getState() + " - " + description);
}
} catch (ApiException e) {
System.err.println("Error: " + e.getMessage());
}
}
}
Compile and run the application:
You should obtain an output similar to this one:
Found 2 notifications for invoice 225
[2025-01-23T16:56:14Z] state=INVIATO - OK
[2025-01-23T17:12:03Z] state=CONSEGNATO - OK
The state field is the most important property. The most common values are:
| Value | Name | Description |
|---|---|---|
| 2 | Inviato |
Sent to the SDI. |
| 5 | Consegnato |
Delivered to the recipient. |
| 7 | Scartato |
Rejected by the SDI. The reason is in description. |
The complete list of values is available in the API Reference.
Always monitor the state of your sent invoices
A state of Inviato only means that the document has been accepted by the SDI, not that it has been delivered. A Scartato state indicates that the invoice was not accepted and may require a correction and a fresh submission.
What we learned
-
To consult the notification history we use the
UpdateApiclass instead ofSendApiorReceiveApi. -
The
updateGet()method accepts filters such assendId(notifications for a specific sent invoice),state(filter by state),lastUpdateFrom/lastUpdateTo(date range) and others. -
/updatequeries are free of charge and do not count against your plan, so you can poll them as often as you need.
Source Code on GitHub
The source code for this Quickstart is also available on GitHub.