---
title: "Installing or upgrading to the v3 Java SDK"
slug: "installing-or-upgrading-to-the-v3-java-sdk"
updated: 2026-06-22T07:56:09Z
published: 2026-06-22T07:56:09Z
canonical: "support.lusid.com/installing-or-upgrading-to-the-v3-java-sdk"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://support.lusid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Installing or upgrading to the v3 Java SDK

> [!WARNING]
> **Important**: This is the latest version. Information about older versions can be found [here](/v1/docs/installing-or-upgrading-to-v2-java-sdks).

The v3 Java SDK is a universal SDK that provides access to LUSID and to all the other applications in the [FINBOURNE platform](https://support.lusid.com/v1/docs/understanding-all-the-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 `&lt;dependencies&gt;` section of your project's `pom.xml` file:

```xml
<dependency>
    <groupId>com.finbourne.sdk</groupId>
    <artifactId>finbourne.sdk</artifactId>
    <version>LATEST</version>
</dependency>
```

Note the following:

- Replace `&lt;version&gt;LATEST&lt;/version&gt;` above with a valid [version number](https://github.com/finbourne/finbourne-sdk-java#installation), for example `&lt;version&gt;0.0.8&lt;/version&gt;`.
- Java 21 or later is required.
- Alternatively, you can download the source by cloning the [GitHub repository](https://github.com/finbourne/finbourne-sdk-java).

## Import statements

Import from `com.finbourne.sdk.extensions` for factory and authentication classes, and from `com.finbourne.sdk.services.&lt;service&gt;` for the API endpoints and models of each FINBOURNE application you intend to use:

```java
// 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](https://support.lusid.com/v1/docs/how-do-i-generate-and-reveal-a-client-secret) 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](/v1/docs/how-do-i-use-an-api-access-token-with-the-lusid-sdk).

You *can* use a long-lived [personal access token](https://support.lusid.com/v1/docs/what-is-a-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`:

```shell
# 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](/v1/docs/installing-or-upgrading-to-the-v3-java-sdk#instantiation) 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:

```json
{
  "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:

1. The `FBN_PROFILE` environment variable, if set.
2. The `withProfileName()` parameter when you build the factory, if specified (see below).
3. The `default` profile.

## 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

```java
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.

```java
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:

```java
// 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:

```java
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:

```java
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();
        }
    }
}
```

![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image-DEJS79S1.png)

For more information on FINBOURNE APIs, start with the articles in this KB, our [API sandbox](https://support.lusid.com/v1/docs/getting-started-with-the-lusid-rest-api-and-sdks), and library of [Jupyter Notebooks](https://github.com/finbourne/finbourne-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:&lt;service&gt;-sdk` and `com.finbourne:&lt;service&gt;-sdk-extensions` dependencies from your `pom.xml` and replace them with the single `com.finbourne.sdk:finbourne.sdk` dependency.
- **Update package imports.** Replace `com.finbourne.lusid.api.*` and `com.finbourne.lusid.model.*` with `com.finbourne.sdk.services.lusid.api.*` and `com.finbourne.sdk.services.lusid.model.*` (and likewise for every other service). Update extensions imports from `com.finbourne.lusid.extensions` to `com.finbourne.sdk.extensions`.
- **Replace per-service factories with one** `ApiFactory`**.** Replace `LusidApiFactory`/`LusidApiFactoryBuilder` (and equivalents for other services) with `com.finbourne.sdk.extensions.ApiFactory`/`ApiFactoryBuilder`.
- **Update your secrets file.** Rename the `apiUrl` key to `baseUrl` and ensure the value points to the root of your domain.
- **Update environment variables.** Replace per-service variables such as `FBN_LUSID_API_URL` with `FBN_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.annotation` *if explicitly declared***.** v3 no longer depends on `jakarta.annotation-api`.

## Appendix B: Environment variable reference

| `FBN_BASE_URL` | Root of the LUSID domain to connect to |
| --- | --- |
| `FBN_TOKEN_URL` | Okta token URL for OAuth2 flow |
| `FBN_USERNAME` | LUSID username for OAuth2 flow |
| `FBN_PASSWORD` | LUSID user password for OAuth2 flow |
| `FBN_CLIENT_ID` | Client ID for OAuth2 flow |
| `FBN_CLIENT_SECRET` | Client secret for OAuth2 flow |
| `FBN_ACCESS_TOKEN` | Personal access token (less secure than OAuth2) |
| `FBN_PROFILE` | Sets the active profile in a `secrets.json` file |
| `FBN_TOTAL_TIMEOUT_MS` | Total timeout configuration option |
| `FBN_CONNECT_TIMEOUT_MS` | Authentication timeout configuration option |
| `FBN_READ_TIMEOUT_MS` | Read timeout configuration option |
| `FBN_WRITE_TIMEOUT_MS` | Write timeout configuration option |
| `FBN_RATE_LIMIT_RETRIES` | Rate limit retries configuration option |
