Skip to content

Repository files navigation

Xero-Java

Xero-JavaGithub forksGithub stars

The Xero-Java SDK makes it easy for developers to access Xero's APIs in their Java code, and build robust applications and software using small business & general ledger accounting data.

Table of Contents


API Client documentation

This SDK supports full method coverage for the following Xero API sets:

API SetDescription
AccountingThe Accounting API exposes accounting functions of the main Xero application (most commonly used)
AssetsThe Assets API exposes fixed asset related functions of the Xero Accounting application
FilesThe Files API provides access to the files, folders, and the association of files within a Xero organisation
FinanceThe Finance API exposes finacial functions that may help lenders gain the confidence they need to provide capital
ProjectsXero Projects allows businesses to track time and costs on projects/jobs and report on profitability
Payroll (AU)The (AU) Payroll API exposes payroll related functions of the payroll Xero application
Payroll (UK)The (UK) Payroll API exposes payroll related functions of the payroll Xero application
Payroll (NZ)The (NZ) Payroll API exposes payroll related functions of the payroll Xero application
Payroll (NZ)The Bankfeeds API exposes Bankfeed functions - this is a restricted API - Contact us to get permission to use

drawing


Sample Applications

Sample apps can get you started quickly with simple auth flows and advanced usage examples.

Sample AppDescription
starter-appBasic getting started code samples
full-appComplete app with more examples
custom-connections-starterBasic app showing Custom Connections - a Xero premium option for building M2M integrations to a single org

Xero Account Requirements

  • Create a free Xero user account
  • Login to your Xero developer dashboard and create an API application
  • Copy the credentials from your API app and store them using a secure ENV variable strategy
  • Decide the necessary scopes for your app's functionality

Installation

Add the Xero Java SDK dependency to project via maven, gradle, sbt or other build tools can be found on maven central.

<dependency>
<groupId>com.github.xeroapi</groupId>
<artifactId>xero-java</artifactId>
<version>4.X.X</version>
</dependency>

Authentication

All API requests go through Xero's OAuth 2.0 gateway and require a valid access_token to be set on the client which appends the access_tokenJWT to the header of each request.

The code below shows how to perform the OAuth 2 authorization code flow.

  1. Authorization.java
  2. Callback.java
  3. TokenStorage.java
  4. TokenRefresh.java

Create your Xero app to obtain your clientId, clientSecret and set your redirectUri. The redirectUri is your server that Xero will send a user back to once authorization is complete (aka callback url).

You can add or remove resources from the scopeList for your integration. We have a list of all available scopes.

Lastly, you'll generate an authorization URL and redirect the user to Xero for authorization.

Authorization.java

packagecom.xero.example;
importjava.io.IOException;
importjava.util.ArrayList;
importjava.util.Random;
importjavax.servlet.ServletException;
importjavax.servlet.annotation.WebServlet;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importcom.google.api.client.auth.oauth2.AuthorizationCodeFlow;
importcom.google.api.client.auth.oauth2.BearerToken;
importcom.google.api.client.auth.oauth2.ClientParametersAuthentication;
importcom.google.api.client.http.GenericUrl;
importcom.google.api.client.http.javanet.NetHttpTransport;
importcom.google.api.client.json.JsonFactory;
importcom.google.api.client.json.jackson2.JacksonFactory;
importcom.google.api.client.util.store.DataStoreFactory;
importcom.google.api.client.util.store.MemoryDataStoreFactory;
@WebServlet("/Authorization")
publicclassAuthorizationextendsHttpServlet {
privatestaticfinallongserialVersionUID = 1L;
finalStringclientId = "--CLIENT-ID--";
finalStringclientSecret = "--CLIENT-SECRET--";
finalStringredirectURI = "http://localhost:8080/starter/Callback";
finalStringTOKEN_SERVER_URL = "https://identity.xero.com/connect/token";
finalStringAUTHORIZATION_SERVER_URL = "https://login.xero.com/identity/connect/authorize";
finalNetHttpTransportHTTP_TRANSPORT = newNetHttpTransport();
finalJsonFactoryJSON_FACTORY = newJacksonFactory();
finalStringsecretState = "secret" + newRandom().nextInt(999_999);
/** * @see HttpServlet#HttpServlet() */publicAuthorization() {
super();
}
/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse)
throwsServletException, IOException {
ArrayList<String> scopeList = newArrayList<String>();
scopeList.add("openid");
scopeList.add("email");
scopeList.add("profile");
scopeList.add("offline_access");
scopeList.add("accounting.settings");
scopeList.add("accounting.transactions");
scopeList.add("accounting.contacts");
scopeList.add("accounting.journals.read");
scopeList.add("accounting.reports.read");
scopeList.add("accounting.attachments");
// Save your secretState variable and compare in callback to prevent CSRFTokenStoragestore = newTokenStorage();
store.saveItem(response, "state", secretState);
DataStoreFactoryDATA_STORE_FACTORY = newMemoryDataStoreFactory();
AuthorizationCodeFlowflow = newAuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
HTTP_TRANSPORT, JSON_FACTORY, newGenericUrl(TOKEN_SERVER_URL),
newClientParametersAuthentication(clientId, clientSecret), clientId, AUTHORIZATION_SERVER_URL)
.setScopes(scopeList).setDataStoreFactory(DATA_STORE_FACTORY).build();
Stringurl = flow.newAuthorizationUrl().setClientId(clientId).setScopes(scopeList).setState(secretState)
.setRedirectUri(redirectURI).build();
response.sendRedirect(url);
}
}

After the user has selected an organisation to authorise, they will be returned to your application specified in the redirectUri. Below is an example Callback servlet. You'll get a code from callback url query string and use it to request you access token.

An access token can be associate with one or more Xero orgs, so you'll need to call Xero's identity service (https://api.xero.com/Connections). You'll receive an array of xero-tenant-id's (that identify the organisation(s) authorized). Use both the access token and the tenant id to access resources via the API.

Lastly, we save the access token, refresh token and Xero tenant id. We've mocked up a TokenStorage class for this demo.

Callback.java

packagecom.xero.example;
importjava.io.IOException;
importjava.util.ArrayList;
importjava.util.List;
importjavax.servlet.ServletException;
importjavax.servlet.annotation.WebServlet;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importcom.auth0.jwt.interfaces.DecodedJWT;
importcom.google.api.client.auth.oauth2.AuthorizationCodeFlow;
importcom.google.api.client.auth.oauth2.BearerToken;
importcom.google.api.client.auth.oauth2.ClientParametersAuthentication;
importcom.google.api.client.auth.oauth2.TokenResponse;
importcom.google.api.client.http.GenericUrl;
importcom.google.api.client.http.javanet.NetHttpTransport;
importcom.google.api.client.json.JsonFactory;
importcom.google.api.client.json.jackson2.JacksonFactory;
importcom.google.api.client.util.store.DataStoreFactory;
importcom.google.api.client.util.store.MemoryDataStoreFactory;
importcom.xero.api.ApiClient;
importcom.xero.api.client.IdentityApi;
importcom.xero.models.identity.Connection;
@WebServlet("/Callback")
publicclassCallbackextendsHttpServlet {
privatestaticfinallongserialVersionUID = 1L;
finalStringclientId = "--CLIENT-ID--";
finalStringclientSecret = "--CLIENT-SECRET--";
finalStringredirectURI = "http://localhost:8080/starter/Callback";
finalStringTOKEN_SERVER_URL = "https://identity.xero.com/connect/token";
finalStringAUTHORIZATION_SERVER_URL = "https://login.xero.com/identity/connect/authorize";
finalNetHttpTransportHTTP_TRANSPORT = newNetHttpTransport();
finalJsonFactoryJSON_FACTORY = newJacksonFactory();
finalApiClientdefaultClient = newApiClient();
/** * @see HttpServlet#HttpServlet() */publicCallback() {
super();
}
/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse)
throwsServletException, IOException {
Stringcode = "123";
if (request.getParameter("code") != null) {
code = request.getParameter("code");
}
// Retrieve your stored secretState variable TokenStoragestore = newTokenStorage();
StringsecretState =store.get(request, "state");
// Compare to state prevent CSRFif (request.getParameter("state") != null && secretState.equals(request.getParameter("state").toString())) {
ArrayList<String> scopeList = newArrayList<String>();
scopeList.add("openid");
scopeList.add("email");
scopeList.add("profile");
scopeList.add("offline_access");
scopeList.add("accounting.settings");
scopeList.add("accounting.transactions");
scopeList.add("accounting.contacts");
scopeList.add("accounting.journals.read");
scopeList.add("accounting.reports.read");
scopeList.add("accounting.attachments");
DataStoreFactoryDATA_STORE_FACTORY = newMemoryDataStoreFactory();
AuthorizationCodeFlowflow = newAuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
HTTP_TRANSPORT, JSON_FACTORY, newGenericUrl(TOKEN_SERVER_URL),
newClientParametersAuthentication(clientId, clientSecret), clientId, AUTHORIZATION_SERVER_URL)
.setScopes(scopeList).setDataStoreFactory(DATA_STORE_FACTORY).build();
TokenResponsetokenResponse = flow.newTokenRequest(code).setRedirectUri(redirectURI).execute();
try {
DecodedJWTverifiedJWT = defaultClient.verify(tokenResponse.getAccessToken());
ApiClientdefaultIdentityClient = newApiClient("https://api.xero.com", null, null, null, null);
IdentityApiidApi = newIdentityApi(defaultIdentityClient);
List<Connection> connection = idApi.getConnections(tokenResponse.getAccessToken(),null);
store.saveItem(response, "token_set", tokenResponse.toPrettyString());
store.saveItem(response, "access_token", verifiedJWT.getToken());
store.saveItem(response, "refresh_token", tokenResponse.getRefreshToken());
store.saveItem(response, "expires_in_seconds", tokenResponse.getExpiresInSeconds().toString());
store.saveItem(response, "xero_tenant_id", connection.get(0).getTenantId().toString());
response.sendRedirect("./AuthenticatedResource");
} catch (Exceptione) {
e.printStackTrace();
}
} else {
System.out.println("Invalid state - possible CSFR");
}
}
}	

TokenStorage class uses cookies to store your access token, refresh token and Xero tenant id. Of course, you'd want to create your own implementation of Token Storage to store information in a database. This class is merely for demo purposes so you can trying out the SDK.

TokenStorage.java

packagecom.xero.example;
importjava.util.HashMap;
importjava.util.Iterator;
importjava.util.Map;
importjava.util.Map.Entry;
importjava.util.Set;
importjavax.servlet.http.Cookie;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
publicclassTokenStorage {
publicTokenStorage() {
super();
}
publicStringget(HttpServletRequestrequest, Stringkey) {
Stringitem = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (inti = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(key)) {
item = cookies[i].getValue();
}
}
}
returnitem;
}
publicvoidclear(HttpServletResponseresponse) {
HashMap<String, String> map = newHashMap<String, String>();
map.put("jwt_token", "");
map.put("id_token", ""); map.put("access_token", "");
map.put("refresh_token", "");
map.put("expires_in_seconds", "");
map.put("xero_tenant_id", "");
save(response, map);
}
publicvoidsaveItem(HttpServletResponseresponse, Stringkey, Stringvalue) {
Cookiet = newCookie(key, value);
response.addCookie(t);
}
publicvoidsave(HttpServletResponseresponse, HashMap<String, String> map) {
Set<Entry<String, String>> set = map.entrySet();
Iterator<Entry<String, String>> iterator = set.iterator();
while (iterator.hasNext()) {
Map.Entry<?, ?> mentry = iterator.next();
Stringkey = (String) mentry.getKey();
Stringvalue = (String) mentry.getValue();
Cookiet = newCookie(key, value);
response.addCookie(t);
}
}
}

TokenRefresh class is an example of how you can check if your access token is expired and perform a token refresh if needed. This example uses the TokenStorage class to persist you new access token and refresh token when performing a refresh. You are welcome to modify or replace this class to suit your needs.

TokenRefresh.java

packagecom.xero.example;
importjava.io.IOException;
importjavax.servlet.http.HttpServletResponse;
importorg.slf4j.LoggerFactory;
importorg.slf4j.Logger;
importcom.auth0.jwt.JWT;
importcom.auth0.jwt.exceptions.JWTDecodeException;
importcom.auth0.jwt.interfaces.DecodedJWT;
importcom.google.api.client.auth.oauth2.RefreshTokenRequest;
importcom.google.api.client.auth.oauth2.TokenResponse;
importcom.google.api.client.auth.oauth2.TokenResponseException;
importcom.google.api.client.http.BasicAuthentication;
importcom.google.api.client.http.GenericUrl;
importcom.google.api.client.http.javanet.NetHttpTransport;
importcom.google.api.client.json.jackson2.JacksonFactory;
importcom.xero.api.ApiClient;
publicclassTokenRefresh {
finalstaticLoggerlogger = LoggerFactory.getLogger(TokenRefresh.class);
finalStringclientId = "--CLIENT-ID--";
finalStringclientSecret = "--CLIENT-SECRET--";
finalStringTOKEN_SERVER_URL = "https://identity.xero.com/connect/token";
finalApiClientdefaultClient = newApiClient();
publicTokenRefresh() {
super();
}
publicStringcheckToken(StringaccessToken, StringrefreshToken, HttpServletResponseresponse) throwsIOException {
StringcurrToken = null;
try {
DecodedJWTjwt = JWT.decode(accessToken);
if (jwt.getExpiresAt().getTime() > System.currentTimeMillis()) {
System.out.println("Refresh Token : NOT NEEDED - return current token");
currToken = accessToken;
} else {
System.out.println("Refresh Token : BEGIN");
try {
TokenResponsetokenResponse = newRefreshTokenRequest(newNetHttpTransport(), newJacksonFactory(),
newGenericUrl(TOKEN_SERVER_URL), refreshToken)
.setClientAuthentication(newBasicAuthentication(this.clientId, this.clientSecret))
.execute();
System.out.println("Refresh Token : SUCCESS");
try {
DecodedJWTverifiedJWT = defaultClient.verify(tokenResponse.getAccessToken());
// DEMO PURPOSE ONLY - You'll need to implement your own token storage solutionTokenStoragestore = newTokenStorage();
store.saveItem(response, "token_set", tokenResponse.toPrettyString());
store.saveItem(response, "access_token", verifiedJWT.getToken());
store.saveItem(response, "refresh_token", tokenResponse.getRefreshToken());
store.saveItem(response, "expires_in_seconds", tokenResponse.getExpiresInSeconds().toString());
currToken = verifiedJWT.getToken();
} catch (Exceptione) {
e.printStackTrace();
} } catch (TokenResponseExceptione) {
System.out.println("Refresh Token : EXCEPTION");
if (e.getDetails() != null) {
System.out.println("Error: " + e.getDetails().getError());
if (e.getDetails().getErrorDescription() != null) {
System.out.println(e.getDetails().getErrorDescription());
}
if (e.getDetails().getErrorUri() != null) {
System.out.println(e.getDetails().getErrorUri());
}
} else {
System.out.println("Refresh Token : EXCEPTION");
System.out.println(e.getMessage());
}
}
}
} catch (JWTDecodeExceptionexception) {
System.out.println("Refresh Token : INVALID TOKEN");
System.out.println(exception.getMessage());
}
returncurrToken;
}
}

It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK client, and refresh your access_token prior to making API calls.

Token Set

keyvaluedescription
id_token:"xxx.yyy.zzz"OpenID Connect token returned if openid profile email scopes accepted
access_token:"xxx.yyy.zzz"Bearer token with a 30 minute expiration required for all API calls
expires_in:1800Time in seconds till the token expires - 1800s is 30m
refresh_token:"XXXXXXX"Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry
scope:"email profile openid accounting.transactions offline_access"The Xero permissions that are embedded in the access_token

Custom Connections

Custom Connections are a Xero premium option used for building M2M integrations to a single organisation. A custom connection uses OAuth 2.0's client_credentials grant which eliminates the step of exchanging the temporary code for a token set.

Sample Application full code example

To use this SDK with a Custom Connection:

finalStringclientId = "--CLIENT-ID--";
finalStringclientSecret = "--CLIENT-SECRET--";
finalNetHttpTransportHTTP_TRANSPORT = newNetHttpTransport();
finalJsonFactoryJSON_FACTORY = newJacksonFactory();
ArrayList<String> appStoreScopeList = newArrayList<String>();
appStoreScopeList.add("accounting.transactions");
// client_credentials TokenResponsetokenResponse = newClientCredentialsTokenRequest(HTTP_TRANSPORT, JSON_FACTORY, newGenericUrl("https://identity.xero.com/connect/token"))
.setScopes(appStoreScopeList)
.setClientAuthentication( newBasicAuthentication(clientId, clientSecret))
.execute();

Because Custom Connections are only valid for a single organisation you don't need to set the specific xero-tenant-id anymore which can now simply be set as an empy string.


App Store Subscriptions

If you are implementing subscriptions to participate in Xero's App Store you will need to setup App Store subscriptions endpoints.

When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API.

With your app credentials you can create a client via client_credentials grant_type with the marketplace.billing scope. This unique access_token will allow you to interact with functions in AppStoreApi. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process.

finalStringclientId = "--APP-STORE-CLIENT-ID--";
finalStringclientSecret = "--APP-STORE-CLIENT-SECRET--";
finalNetHttpTransportHTTP_TRANSPORT = newNetHttpTransport();
finalJsonFactoryJSON_FACTORY = newJacksonFactory();
ArrayList<String> appStoreScopeList = newArrayList<String>();
scopesList.add("marketplace.billing");
// client_credentials TokenResponsetokenResponse = newClientCredentialsTokenRequest(HTTP_TRANSPORT, JSON_FACTORY, newGenericUrl("https://identity.xero.com/connect/token"))
.setScopes(scopesList)
.setClientAuthentication( newBasicAuthentication(clientId, clientSecret))
.execute();
// => /post-purchase-url?subscriptionId=03bc74f2-1237-4477-b782-2dfb1a6d8b21AppStoreApiappStoreApi = newAppStoreApi(defaultIdentityClient);
Subscriptionsubscription = appStoreApi.getSubscription(tokenResponse.getAccessToken(), "03bc74f2-1237-4477-b782-2dfb1a6d8b21");

API Clients

You can access the different API sets and their available methods through the following:

ApiClientdefaultClient = newApiClient();
// Get Singleton - instance of sub clientaccountingApi = AccountingApi.getInstance(defaultClient);
assetApi = AssetApi.getInstance(defaultClient);
bankFeedsApi = BankfeedsApi.getInstance(defaultClient);
filesApi = FilesApi.getInstance(defaultClient);
projectApi = ProjectNzApi.getInstance(defaultClient);
identityApi = IdentityApi.getInstance(defaultClient);
payrollAuApi = PayrollAuApi.getInstance(defaultClient);
payrollUkApi = PayrollUkApi.getInstance(defaultClient);
payrollNzApi = PayrollNzApi.getInstance(defaultClient);
appStoreApi = AppStoreApi.getInstance(defaultClient);

Usage Examples

The Xero Java SDK contains Client classes (AccountingApi, etc) which have helper methods to perform (Create, Read, Update and Delete) actions on each endpoints. AccountingApi is designed as a Singleton. Use the getInstance method of the class class and use with API models to interact with Java Objects.

Token expiration should be checked prior to making API calls

AuthenticatedResource.java

packagecom.xero.example;
importjava.io.IOException;
importjava.util.UUID;
importjavax.servlet.ServletException;
importjavax.servlet.annotation.WebServlet;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.threeten.bp.OffsetDateTime;
importcom.xero.api.ApiClient;
importcom.xero.api.XeroApiException;
importcom.xero.api.client.AccountingApi;
importcom.xero.models.accounting.*;
@WebServlet("/AuthenticatedResource")
publicclassAuthenticatedResourceextendsHttpServlet {
privatestaticfinallongserialVersionUID = 1L;
privateAccountingApiaccountingApi;
publicAuthenticatedResource() {
super();
}
protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException {
// Get Tokens and Xero Tenant Id from StorageTokenStoragestore = newTokenStorage();
StringsavedAccessToken =store.get(request, "access_token");
StringsavedRefreshToken = store.get(request, "refresh_token");
StringxeroTenantId = store.get(request, "xero_tenant_id"); // Check expiration of token and refresh if necessary// This should be done prior to each API call to ensure your accessToken is validStringaccessToken = newTokenRefresh().checkToken(savedAccessToken,savedRefreshToken,response);
// Init AccountingApi clientApiClientdefaultClient = newApiClient();
// Get Singleton - instance of accounting clientaccountingApi = AccountingApi.getInstance(defaultClient); try {
// Get All ContactsContactscontacts = accountingApi.getContacts(accessToken,xeroTenantId,null, null, null, null, null, null);
System.out.println("How many contacts did we find: " + contacts.getContacts().size());
/* CREATE ACCOUNT */Accountacct = newAccount();
acct.setName("Office Expense for Me");
acct.setCode("66000");
acct.setType(com.xero.models.accounting.AccountType.EXPENSE);
AccountsnewAccount = accountingApi.createAccount(accessToken,xeroTenantId,acct);
System.out.println("New account created: " + newAccount.getAccounts().get(0).getName());
/* READ ACCOUNT using a WHERE clause */Stringwhere = "Status==\"ACTIVE\"&&Type==\"BANK\"";
AccountsaccountsWhere = accountingApi.getAccounts(accessToken,xeroTenantId,null, where, null);
/* READ ACCOUNT using the ID */Accountsaccounts = accountingApi.getAccounts(accessToken,xeroTenantId,null, null, null);
UUIDaccountID = accounts.getAccounts().get(0).getAccountID();
AccountsoneAccount = accountingApi.getAccount(accessToken,xeroTenantId,accountID);
/* UPDATE ACCOUNT */UUIDnewAccountID = newAccount.getAccounts().get(0).getAccountID();
newAccount.getAccounts().get(0).setDescription("Monsters Inc.");
newAccount.getAccounts().get(0).setStatus(null);
AccountsupdateAccount = accountingApi.updateAccount(accessToken,xeroTenantId,newAccountID, newAccount);
/* DELETE ACCOUNT */UUIDdeleteAccountID = newAccount.getAccounts().get(0).getAccountID();
AccountsdeleteAccount = accountingApi.deleteAccount(accessToken,xeroTenantId,deleteAccountID);
System.out.println("Delete account - Status? : " + deleteAccount.getAccounts().get(0).getStatus());
// GET INVOICE MODIFIED in LAST 24 HOURSOffsetDateTimeinvModified = OffsetDateTime.now();
invModified.minusDays(1); InvoicesInvoiceList24hour = accountingApi.getInvoices(accessToken,xeroTenantId,invModified, null, null, null, null, null, null, null, null, null, null);
System.out.println("How many invoices modified in last 24 hours?: " + InvoiceList24hour.getInvoices().size());
response.getWriter().append("API calls completed at: ").append(request.getContextPath());
} catch (XeroBadRequestExceptione) {
// 400// ACCOUNTING VALIDATION ERRORif (e.getElements() != null && e.getElements().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (Elementitem : e.getElements()) {
for (ValidationErrorerr : item.getValidationErrors()) {
System.out.println("Accounting Validation Error Msg: " + err.getMessage());
}
}
}
} catch (XeroUnauthorizedExceptione) {
// 401System.out.println("Exception message: " + e.getMessage());
} catch (XeroForbiddenExceptione) {
// 403System.out.println("Exception message: " + e.getMessage());
} catch (XeroNotFoundExceptione) {
// 404System.out.println("Exception message: " + e.getMessage());
} catch (XeroMethodNotAllowedExceptione) {
// 405System.out.println("Exception message: " + e.getMessage());
} catch (XeroRateLimitExceptione) {
// 429System.out.println("Exception message: " + e.getMessage()); } catch (XeroServerErrorExceptione) {
// 500System.out.println("Exception message: " + e.getMessage());
} catch (Exceptione) {
System.out.println(e.getMessage());
} }
}

Revoking Token

You can revoke a user's refresh token and remove all their connections to your app by making a request to the revocation endpoint.

We've added a helpful method to the ApiClient class. The code below shows how to pass the id, secret and refresh token to execute the revoke method. Success

try {
ApiClientapiClient = newApiClient();
HttpResponserevokeResponse = apiClient.revoke(clientId, clientSecret, refreshToken);
System.out.println("Revoke success: " + revokeResponse.getStatusCode());
} catch (Exceptione) {
System.out.println(e.getMessage());
}

Running Test(s) in Local

For Running Test cases PRISM Mock Server needs to be started in the local machine. Steps to Run Test(s)

  • Install PRISM from npm using the command: npm install -g @stoplight/prism-cli
  • Verify Installation: prism --version
  • Navigate to Xero-Java--> src--> test--> util folder in the terminal
  • Execute the script ./start-prism.sh
  • This will start the PRISM Server in Local
  • Run mvn clean verify -DskipTests=false to build the Java code along with Test Cases.

SDK conventions

Working with dates

Both our Accounting and AU Payroll APIs use Microsoft .NET JSON format i.e. "/Date(1439434356790)/". Our other APIs use standard date formatting i.e. "2020-03-24T18:43:43.860852". Building our SDKs from OpenAPI specs with such different date formats has been challenging.

For this reason, we've decided dates in MS .NET JSON format will be strings with NO date or date-time format in our OpenAPI specs. This means developers wanting to use our OpenAPI specs with code generators won't run into deserialization issues trying to handle MS .NET JSON format dates.

The side effect is accounting and AU payroll models now have two getter methods. For example, getDateOfBirth() returns the string "/Date(1439434356790)/" while getDateOfBirthAsDate() return a standard date "2020-05-14". Since you can overload methods in Java setDateOfBirth() can accept a String or a LocalDate.

This is a breaking change between version 3.x and 4.x.

Exception Handling

As we work to expand API coverage in our SDKs through new OpenAPI specs, we've discovered that error messages returned by different Xero API sets can vary greatly. Specifically, we found the format of validation errors differ enough that our current exception handling resulted in details being lost as exceptions bubbled up.

To address this we've refactored exception handling and we are deprecating the general purpose XeroApiException class and replacing it with unique exceptions. Below are the unique exception classes, but will add more as needed.

This is a breaking change between version 3.x and 4.x.

codeclassdescription
N/AXeroExceptionAll Xero exceptions extend from XeroException
N/AXeroAuthenticationExceptionXeroUnauthorizedException and XeroUnauthorizedException extend from XeroAuthenticationException
400XeroBadRequestExceptionA validation exception has occurred - typical cause invalid data. Look at data returned for error details
401XeroUnauthorizedExceptionInvalid authorization credentials. Extends XeroAuthenticationException
403XeroForbiddenExceptionNot authorized to access a resource - typical cause is problem with scopes. Extends XeroAuthenticationException
404XeroNotFoundExceptionThe resource you have specified cannot be found
405XeroMethodNotAllowedExceptionMethod not allowed on the organisation - typical cause the API is not available in the organisation i.e. UK Payroll on Australian org.
429XeroRateLimitExceptionAll Xero rate limit exceptions extend XeroRateLimitException.
429XeroAppMinuteRateLimitExceptionThe API app minute rate limit for your organisation/application pairing has been exceeded.
429XeroDailyRateLimitExceptionThe API daily rate limit for your organisation/application pairing has been exceeded.
429XeroMinuteRateLimitExceptionThe API minute rate limit for your organisation/application pairing has been exceeded.
500XeroServerErrorExceptionAn unhandled error with the Xero API
501XeroNotImplementedExceptionMethod not implemented for the organisation
503XeroNotAvailableExceptionThe organisation temporarily cannot be connected to or API is currently unavailable – typically due to a scheduled outage

Below is a try/catch example

classTryCatchExample {
voidsomeOperation() {
try {
// Create contact with the same name as an existing contact will generate a validation error.Contactcontact = newContact();
contact.setName("Test user");
ContactscreateContact1 = accountingApi.createContact(accessToken, xeroTenantId, contact);
ContactscreateContact2 = accountingApi.createContact(accessToken, xeroTenantId, contact);
} catch (XeroBadRequestExceptione) {
// 400// ACCOUNTING VALIDATION ERRORif (e.getElements() != null && e.getElements().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (Elementitem : e.getElements()) {
for (ValidationErrorerr : item.getValidationErrors()) {
System.out.println("Accounting Validation Error Msg: " + err.getMessage());
}
}
// FIXED ASSETS VALIDATION ERROR
} elseif (e.getFieldValidationErrorsElements() != null && e.getFieldValidationErrorsElements().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (FieldValidationErrorsElementele : e.getFieldValidationErrorsElements()) {
System.out.println("Asset Field Validation Error Msg: " + ele.getDetail());
}
// BANKFEEDS - Statement VALIDATION ERROR
} elseif (e.getStatementItems() != null && e.getStatementItems().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (Statementstatement : e.getStatementItems()) {
System.out.println("Bank Feed - Statement Msg: " + statement.getFeedConnectionId());
for (com.xero.models.bankfeeds.ErrorstatementError : statement.getErrors()) {
System.out.println("Bank Feed - Statement Error Msg: " + statementError.getDetail());
}
}
// AU PAYROLL - Employee VALIDATION ERROR
} elseif (e.getEmployeeItems() != null && e.getEmployeeItems().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (com.xero.models.payrollau.Employeeemp : e.getEmployeeItems()) {
for (com.xero.models.payrollau.ValidationErrorerr : emp.getValidationErrors()) {
System.out.println("Payroll AU Employee Validation Error Msg: " + err.getMessage());
}
}
// AU PAYROLL - Payroll Calendar VALIDATION ERROR
} elseif (e.getPayrollCalendarItems() != null && e.getPayrollCalendarItems().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (com.xero.models.payrollau.PayrollCalendaritem : e.getPayrollCalendarItems()) {
for (com.xero.models.payrollau.ValidationErrorerr : item.getValidationErrors()) {
System.out.println("Payroll AU Payroll Calendar Validation Error Msg: " + err.getMessage());
}
}
// AU PAYROLL - PayRun VALIDATION ERROR
} elseif (e.getPayRunItems() != null && e.getPayRunItems().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (com.xero.models.payrollau.PayRunitem : e.getPayRunItems()) {
for (com.xero.models.payrollau.ValidationErrorerr : item.getValidationErrors()) {
System.out.println("Payroll AU Payroll Calendar Validation Error Msg: " + err.getMessage());
}
}
// AU PAYROLL - SuperFund VALIDATION ERROR
} elseif (e.getSuperFundItems() != null && e.getSuperFundItems().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (com.xero.models.payrollau.SuperFunditem : e.getSuperFundItems()) {
for (com.xero.models.payrollau.ValidationErrorerr : item.getValidationErrors()) {
System.out.println("Payroll AU SuperFund Validation Error Msg: " + err.getMessage());
}
}
// AU PAYROLL - Timesheet VALIDATION ERROR
} elseif (e.getTimesheetItems() != null && e.getTimesheetItems().size() > 0) {
System.out.println("Xero Exception: " + e.getStatusCode());
for (com.xero.models.payrollau.Timesheetitem : e.getTimesheetItems()) {
for (com.xero.models.payrollau.ValidationErrorerr : item.getValidationErrors()) {
System.out.println("Payroll AU Timesheet Validation Error Msg: " + err.getMessage());
}
}
// UK PAYROLL - PROBLEM ERROR
} elseif (e.getPayrollUkProblem() != null &&
((e.getPayrollUkProblem().getDetail() != null && e.getPayrollUkProblem().getTitle() != null) ||
(e.getPayrollUkProblem().getInvalidFields() != null &&
e.getPayrollUkProblem().getInvalidFields().size() > 0))) {
System.out.println("Xero Exception: " + e.getStatusCode());
System.out.println("Problem title: " + e.getPayrollUkProblem().getTitle());
System.out.println("Problem detail: " + e.getPayrollUkProblem().getDetail());
if (e.getPayrollUkProblem().getInvalidFields() != null && e.getPayrollUkProblem().getInvalidFields().size() > 0) {
for (com.xero.models.payrolluk.InvalidFieldfield : e.getPayrollUkProblem().getInvalidFields()) {
System.out.println("Invalid Field name: " + field.getName());
System.out.println("Invalid Field reason: " + field.getReason());
}
}
} else {
System.out.println("Error Msg: " + e.getMessage());
}
} catch (XeroUnauthorizedExceptione) {
// 401System.out.println("Exception status code: " + e.getStatusCode());
System.out.println("Exception message: " + e.getMessage());
} catch (XeroForbiddenExceptione) {
// 403System.out.println("Exception status code: " + e.getStatusCode());
System.out.println("Exception message: " + e.getMessage());
} catch (XeroNotFoundExceptione) {
// 404System.out.println("Exception status code: " + e.getStatusCode());
System.out.println("Exception message: " + e.getMessage());
} catch (XeroMethodNotAllowedExceptione) {
// 405if (e.getPayrollUkProblem() != null) {
System.out.println("Xero Exception: " + e.getStatusCode());
System.out.println("Problem title: " + e.getPayrollUkProblem().getTitle());
System.out.println("Problem detail: " + e.getPayrollUkProblem().getDetail());
if (e.getPayrollUkProblem().getInvalidFields() != null && e.getPayrollUkProblem().getInvalidFields().size() > 0) {
for (com.xero.models.payrolluk.InvalidFieldfield : e.getPayrollUkProblem().getInvalidFields()) {
System.out.println("Invalid Field name: " + field.getName());
System.out.println("Invalid Field reason: " + field.getReason());
}
}
}
} catch (XeroAppMinuteRateLimitException | XeroDailyRateLimitException | XeroMinuteRateLimitExceptione) {
// 429System.out.println("Exception status code: " + e.getStatusCode());
System.out.println("Exception message: " + e.getMessage());
System.out.println("Remaining app minute limit: " + e.getAppLimitRemaining());
System.out.println("Remaining daily limit: " + e.getDayLimitRemaining());
System.out.println("Remaining minute limit: " + e.getMinuteLimitRemaining());
System.out.println("Retry after seconds: " + e.getRetryAfterSeconds());
} catch (XeroRateLimitExceptione) {
// 429System.out.println("Exception status code: " + e.getStatusCode());
System.out.println("Exception message: " + e.getMessage());
System.out.println("Remaining app minute limit: " + e.getAppLimitRemaining());
System.out.println("Remaining daily limit: " + e.getDayLimitRemaining());
System.out.println("Remaining minute limit: " + e.getMinuteLimitRemaining());
System.out.println("Retry after seconds: " + e.getRetryAfterSeconds());
} catch (XeroServerErrorExceptione) {
// 500System.out.println("Exception status code: " + e.getStatusCode());
System.out.println("Exception message: " + e.getMessage());
} catch (Exceptione) {
// other Exceptions 
}
}
}

Logging

We've replace a specific logging plugin (org.apache.logging.log4j) with a logging facade org.slf4j. With version 4.x we'll use SLF4J and allow you to plug in the logging library of your choice at deployment time. This blog post explains how to add log4j2 for logging. To configure, add a log4j.properties file to the Resources directory.

Looking for version 3.x of the SDK?

Codebase, samples and setup instructions located in java-3.x branch.


Participating in Xero’s developer community

This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes.

Here are a few things you should be aware of as a contributor:

  • Xero has adopted the Contributor Covenant Code of Conduct, we expect all contributors in our community to adhere to it
  • If you raise an issue then please make sure to fill out the Github issue template, doing so helps us help you
  • You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code
  • We have a contribution guide for you to follow when contributing to this SDK
  • Curious about how we generate our SDK’s? Have a read of our process and have a look at our OpenAPISpec
  • This software is published under the MIT License

For questions that aren’t related to SDKs please refer to our developer support page.

Contributing

PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on Xero's OpenAPI specs - PR's will be evaluated and pre-merge will be incorporated into the root generation templates.

Versioning

We do our best to keep OS industry semver standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know.

About

Official Java client for use with Xero API

Topics

Resources

Code of conduct

Contributing

Stars

88 stars

Watchers

14 watching

Forks

Releases

Packages

Used by

Contributors

Languages