Important: This is the latest version. Information about older versions can be found here.
The v3 Java SDK is a universal SDK that provides access to LUSID and to all the other applications in the FINBOURNE platform from a single Maven artifact in Java 21+ environments.
You no longer need to declare and authenticate to separate per-service SDK dependencies to integrate LUSID with applications such as Drive, Workflow Service, Luminese and so on.
Installation
To get started, add the following to the <dependencies> section of your project's pom.xml file:
<dependency>
<groupId>com.finbourne.sdk</groupId>
<artifactId>finbourne.sdk</artifactId>
<version>LATEST</version>
</dependency>Note the following:
Replace
<version>LATEST</version>above with a valid version number, for example<version>0.0.8</version>.Java 21 or later is required.
Alternatively, you can download the source by cloning the GitHub repository.
Import statements
Import from com.finbourne.sdk.extensions for factory and authentication classes, and from com.finbourne.sdk.services.<service> for the API endpoints and models of each FINBOURNE application you intend to use:
// Factory and extensions:
import com.finbourne.sdk.extensions.ApiFactory;
import com.finbourne.sdk.extensions.ApiFactoryBuilder;
import com.finbourne.sdk.core.config.ApiConfigurationException;
import com.finbourne.sdk.core.config.ConfigurationOptions;
import com.finbourne.sdk.core.auth.FinbourneTokenException;
import com.finbourne.sdk.ApiException;
// To use LUSID:
import com.finbourne.sdk.services.lusid.api.PortfoliosApi;
import com.finbourne.sdk.services.lusid.api.TransactionPortfoliosApi;
import com.finbourne.sdk.services.lusid.model.Portfolio;
import com.finbourne.sdk.services.lusid.model.CreateTransactionPortfolioRequest;
// To use Drive:
import com.finbourne.sdk.services.drive.api.FilesApi;
// To use Identity:
import com.finbourne.sdk.services.identity.api.RolesApi;Authentication
The most secure method is OAuth2. If you haven't already, create an SDK application encapsulating a client ID, secret, and token URL, and store them with the credentials of a valid LUSID user as either environment variables or in a secrets file. The SDK uses these credentials to obtain a short-lived access token from FINBOURNE's identity provider (Okta) on demand, and refreshes it automatically.
Note: If environment variables are set, they are preferred over a secrets file. More information.
You can use a long-lived personal access token (PAT) instead of OAuth2, but note this is less secure.
Using environment variables
All applications share a FBN_BASE_URL pointing to the root of your LUSID domain, for example acme-prod.lusid.com:
# OAuth2 (more secure):
FBN_BASE_URL=https://acme-prod.lusid.com
FBN_TOKEN_URL=https://acme-prod.identity.lusid.com/oauth2/.../v1/token
FBN_USERNAME=...
FBN_PASSWORD=...
FBN_CLIENT_ID=...
FBN_CLIENT_SECRET=...
# PAT:
FBN_BASE_URL=https://acme-prod.lusid.com
FBN_ACCESS_TOKEN=...Using a secrets file
A secrets file is a JSON document containing named profiles, one per LUSID domain you wish to connect to. The file must have at least one default profile. You can switch between profiles at build time to connect to different domains.
The following example has two profiles: default connects to acme-prod.lusid.com using OAuth2, and staging connects to acme-sit.lusid.com using a PAT:
{
"profiles": {
"default": {
"tokenUrl": "https://acme-prod.identity.lusid.com/oauth2/.../v1/token",
"baseUrl": "https://acme-prod.lusid.com",
"username": "...",
"password": "...",
"clientId": "...",
"clientSecret": "..."
},
"staging": {
"baseUrl": "https://acme-sit.lusid.com",
"accessToken": "..."
}
}
}The active profile is determined in the following order:
The
FBN_PROFILEenvironment variable, if set.The
withProfileName()parameter when you build the factory, if specified (see below).The
defaultprofile.
Instantiation
Build an ApiFactory using ApiFactoryBuilder, then call build() for each API class you want to use. API instances are cached per factory, so calling build() with the same class always returns the same instance.
If you are using environment variables you can omit withConfigurationFile(). If you are using a secrets file, you can optionally call withProfileName() to connect to a particular domain dynamically.
Synchronous calls
ApiFactory factory = new ApiFactoryBuilder()
.withConfigurationFile("secrets.json")
.withProfileName("staging") // optional
.build();
// Build the API instances you need:
TransactionPortfoliosApi transactionPortfoliosApi = factory.build(TransactionPortfoliosApi.class);
PortfoliosApi portfoliosApi = factory.build(PortfoliosApi.class);
FilesApi filesApi = factory.build(FilesApi.class);
// Call methods:
ResourceListOfPortfolio portfolios = portfoliosApi.listPortfoliosForScope("my-scope")
.effectiveAt("2024-01-01")
.limit(10)
.execute();Asynchronous calls
executeAsync() returns immediately and runs the request on a background thread via an ApiCallback. Bridge the callback to a CompletableFuture to compose on the result asynchronously, and only block at shutdown if necessary to prevent a short-lived program from exiting before the response arrives.
import com.finbourne.sdk.ApiCallback;
import com.finbourne.sdk.services.lusid.api.ApplicationMetadataApi;
import com.finbourne.sdk.services.lusid.model.VersionSummaryDto;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
ApplicationMetadataApi metadataApi = factory.build(ApplicationMetadataApi.class);
CompletableFuture<VersionSummaryDto> future = new CompletableFuture<>();
metadataApi.getLusidVersions().executeAsync(new ApiCallback<>() {
@Override
public void onSuccess(VersionSummaryDto result, int statusCode, Map<String, List<String>> headers) {
future.complete(result);
}
@Override
public void onFailure(ApiException e, int statusCode, Map<String, List<String>> headers) {
future.completeExceptionally(e);
}
@Override public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {}
@Override public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {}
});
future.thenAccept(version -> System.out.println("LUSID API version: " + version.getApiVersion()))
.exceptionally(error -> { System.err.println("Failed: " + error.getMessage()); return null; });
// Block at shutdown only:
future.join();Configuration options
You can optionally configure timeout and retry settings using ConfigurationOptions, either globally at factory level or as overrides per individual API call:
// Set globally at factory level:
ConfigurationOptions opts = new ConfigurationOptions(
30000, // totalTimeoutMs
5000, // connectTimeoutMs
5000, // readTimeoutMs
5000, // writeTimeoutMs
5 // rateLimitRetries
);
ApiFactory factory = new ApiFactoryBuilder()
.withConfigurationFile("secrets.json")
.withConfigurationOptions(opts)
.build();
// Override per API call:
ConfigurationOptions callOpts = new ConfigurationOptions();
callOpts.setTotalTimeoutMs(60000);
callOpts.addHeader("X-Request-Id", "abc-123");
Portfolio portfolio = portfoliosApi.getPortfolio("scope", "code")
.execute(callOpts);Default headers for all requests can also be set at factory level:
ApiFactory factory = new ApiFactoryBuilder()
.withConfigurationFile("secrets.json")
.withHeaders(Map.of("X-Correlation-Id", "session-1"))
.build();Hello world
The following example authenticates using a secrets file, creates a transaction portfolio, reads it back, and cleans up:
import com.finbourne.sdk.ApiException;
import com.finbourne.sdk.core.config.ApiConfigurationException;
import com.finbourne.sdk.core.config.ConfigurationOptions;
import com.finbourne.sdk.core.auth.FinbourneTokenException;
import com.finbourne.sdk.extensions.ApiFactory;
import com.finbourne.sdk.extensions.ApiFactoryBuilder;
import com.finbourne.sdk.services.lusid.api.PortfoliosApi;
import com.finbourne.sdk.services.lusid.api.TransactionPortfoliosApi;
import com.finbourne.sdk.services.lusid.model.CreateTransactionPortfolioRequest;
import com.finbourne.sdk.services.lusid.model.Portfolio;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
public class Example {
public static void main(String[] args)
throws ApiConfigurationException, FinbourneTokenException, ApiException {
// 1. Build one factory for every service.
ApiFactory factory = new ApiFactoryBuilder()
.withConfigurationFile("secrets.json")
.build();
// 2. Build the API instances you need.
TransactionPortfoliosApi transactionPortfoliosApi = factory.build(TransactionPortfoliosApi.class);
PortfoliosApi portfoliosApi = factory.build(PortfoliosApi.class);
String scope = "examples";
String code = "example-portfolio";
// Optional: per-request timeout override.
ConfigurationOptions opts = new ConfigurationOptions();
opts.setTotalTimeoutMs(180_000);
// 3. Create a transaction portfolio.
CreateTransactionPortfolioRequest request = new CreateTransactionPortfolioRequest()
.code(code)
.displayName("Example portfolio")
.baseCurrency("USD")
.created(OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC));
try {
Portfolio created = transactionPortfoliosApi.createPortfolio(scope, request)
.execute(opts);
System.out.println("Created portfolio: " + created.getId().getCode());
// 4. Read it back. Required params in the method, optionals as chained setters.
Portfolio fetched = portfoliosApi.getPortfolio(scope, code)
.effectiveAt("2024-01-01")
.execute();
System.out.println("Base currency: " + fetched.getBaseCurrency());
} catch (ApiException e) {
System.err.println("Request failed (" + e.getCode() + "): " + e.getResponseBody());
} finally {
// 5. Clean up.
portfoliosApi.deletePortfolio(scope, code).execute();
}
}
}
For more information on FINBOURNE APIs, start with the articles in this KB, our API sandbox, and library of Jupyter Notebooks.
Appendix A: Migration checklist
The fluent request-builder calling style introduced in v2 is unchanged in v3, so the migration is mostly about packaging, dependencies, and configuration rather than rewriting call sites.
Replace per-service dependencies with a single artifact. Remove all
com.finbourne:<service>-sdkandcom.finbourne:<service>-sdk-extensionsdependencies from yourpom.xmland replace them with the singlecom.finbourne.sdk:finbourne.sdkdependency.Update package imports. Replace
com.finbourne.lusid.api.*andcom.finbourne.lusid.model.*withcom.finbourne.sdk.services.lusid.api.*andcom.finbourne.sdk.services.lusid.model.*(and likewise for every other service). Update extensions imports fromcom.finbourne.lusid.extensionstocom.finbourne.sdk.extensions.Replace per-service factories with one
ApiFactory. ReplaceLusidApiFactory/LusidApiFactoryBuilder(and equivalents for other services) withcom.finbourne.sdk.extensions.ApiFactory/ApiFactoryBuilder.Update your secrets file. Rename the
apiUrlkey tobaseUrland ensure the value points to the root of your domain.Update environment variables. Replace per-service variables such as
FBN_LUSID_API_URLwithFBN_BASE_URL, pointing to the root of your domain.Upgrade your Java runtime to 21+. v2 compiled to Java 1.8; v3 requires Java 21 or later.
Update JSON handling. v2 used Gson; v3 uses Jackson 2.18.6. Update any code that relies on Gson annotations or type adapters from the model classes.
Remove
jakarta.annotationif explicitly declared. v3 no longer depends onjakarta.annotation-api.
Appendix B: Environment variable reference
| Root of the LUSID domain to connect to |
| Okta token URL for OAuth2 flow |
| LUSID username for OAuth2 flow |
| LUSID user password for OAuth2 flow |
| Client ID for OAuth2 flow |
| Client secret for OAuth2 flow |
| Personal access token (less secure than OAuth2) |
| Sets the active profile in a |
| Total timeout configuration option |
| Authentication timeout configuration option |
| Read timeout configuration option |
| Write timeout configuration option |
| Rate limit retries configuration option |