Migrate from v1 to v2

How to migrate from the v1 SDK to the v2 SDK.

The Java v1 SDK was deprecated on 1 January 2024.

Differences between v1 and v2 SDKs

See the following links for more information on differences between the v1 and v2 SDKs.

Migration guides

You can follow one of these approaches to migrate from Java v1 to v2.

Placeholder values

Example code in this guide uses the following placeholder values. You should replace these placeholders with the following values:

PlaceholderReplace withFrom
{projectKey}project_keyyour API Client
{clientID}client_idyour API Client
{clientSecret}secretyour API Client
{scope}scopeyour API Client
{region}your RegionHosts

Manually migrate to v2 SDK

The following example code demonstrates how to perform an action using the Java v1 SDK, and the best practice for replicating this action with the Java v2 SDK.

Client configuration and creation

SphereClientFactory has been replaced by ApiRootBuilder.

The benefit is that after the ApiRoot has been instantiated, you can create requests directly from it.

Example code from Java v1 SDKJava
final SphereClient sphereClient = SphereClientFactory
.of()
.createClient("{projectKey}", "{clientID}", "{clientSecret}");
Example code for Java v2 SDKJava
final ProjectApiRoot projectRoot = ApiRootBuilder
.of()
.defaultClient(
ClientCredentials
.of()
.withClientId("{clientID}")
.withClientSecret("{clientSecret}")
.build(),
ServiceRegion.GCP_EUROPE_WEST1
)
.build("{projectKey}");

Timeout setting

Both SDKs use the method executeBlocking() to set the timeout.

Example code from Java v1 SDKJava
PagedQueryResult<Category> response = blockingClient()
.executeBlocking(CategoryQuery.of(), 45, TimeUnit.SECONDS);
Example code for Java v2 SDKJava
CategoryPagedQueryResponse response = projectClient()
.categories()
.get()
.executeBlocking(Duration.ofSeconds(45))
.getBody();

Exception handling

In the v1 SDK, the SphereClient throws the underlying exception when it occurs. In the v2 SDK, the ProjectRoot throws an CompletionException instead and the actual exception is wrapped inside it.

The ClientBuilder.withErrorMiddleware(ErrorMiddleware.ExceptionMode) method allows you to configure the middleware to unwrap the causing exception.

Example code from Java v2 SDKJava
final ProjectApiRoot projectRoot = ApiRootBuilder
.of()
.defaultClient(
ClientCredentials
.of()
.withClientId("{clientID}")
.withClientSecret("{clientSecret}")
.build(),
ServiceRegion.GCP_EUROPE_WEST1
)
.withErrorMiddleware(
ErrorMiddleware.ExceptionMode.UNWRAP_COMPLETION_EXCEPTION
)
.build("{projectKey}");

Headers

To set headers, in the v1 SDK there is the HttpRequest class and in the v2 SDK the ApiHttpRequest class.

The main difference, as you can see in the example below, is that the ApiHttpRequest can be directly instantiated and it can set the type of method (GET or POST), the URI, the headers, and the body.

Example code from Java v1 SDKJava
// Not in SDK v1
Example code for Java v2 SDKJava
final CartPagedQueryResponse carts = projectClient()
.carts()
.get()
.addHeader("foo", "bar")
.executeBlocking()
.getBody();

Implementing retry

The retry mechanisms are significantly different between the v1 SDK and the v2 SDK.

In the v1 SDK, the retry mechanism has to be defined piece by piece: first the retry rules, then the SphereClient, and then the request, in this case, a PagedQueryResult

In the v2 SDK, the setup of the request can be built directly during the client creation. ByProjectKeyRequestBuilder can be created with a built-in retry mechanism using the RetryMiddleware. It is also possible to set up additional parameters to the request, like the logger InternalLoggerFactory.

Example code from Java v1 SDKJava
final int maxAttempts = 5;
final List<RetryRule> retryRules = singletonList(
RetryRule.of(
RetryPredicate.ofMatchingStatusCodes(
BAD_GATEWAY_502,
SERVICE_UNAVAILABLE_503,
GATEWAY_TIMEOUT_504
),
RetryAction.ofExponentialBackoff(maxAttempts, 100, 2000)
)
);
final SphereClient client = RetrySphereClientDecorator.of(
sphereClient(),
retryRules
);
final PagedQueryResult<Category> categoryPagedQueryResult = client
.execute(CategoryQuery.of())
.toCompletableFuture()
.get();
Example code for Java v2 SDKJava
final ByProjectKeyRequestBuilder projectClient = ApiRootBuilder
.of()
.defaultClient(
ServiceRegion.GCP_EUROPE_WEST1.getApiUrl(),
ClientCredentials
.of()
.withClientId("{clientId}")
.withClientSecret("{clientSecret}")
.build(),
ServiceRegion.GCP_EUROPE_WEST1.getOAuthTokenUrl()
)
.withRetryMiddleware(5, Arrays.asList(502, 503, 504))
.buildForProject("{projectKey}");
final CategoryPagedQueryResponse body = projectClient
.categories()
.get()
.executeBlocking()
.getBody();

Using DraftBuilders

In the v2 SDK there are not inheritances for DraftBuilder classes, but the differences are minor.

Example code from Java v1 SDKJava
LocalizedString name = LocalizedString.ofEnglish("name");
LocalizedString slug = LocalizedString.ofEnglish("slug");
LocalizedString metaDescription = LocalizedString.ofEnglish("metaDescription");
LocalizedString metaTitle = LocalizedString.ofEnglish("metaTitle");
LocalizedString metaKeywords = LocalizedString.ofEnglish("metaKeywords");
CategoryDraft categoryDraft = CategoryDraftBuilder
.of(name, slug)
.metaDescription(metaDescription)
.metaTitle(metaTitle)
.metaKeywords(metaKeywords)
.externalId("externalId")
.build();
Example code for Java v2 SDKJava
CategoryDraft categoryDraft = CategoryDraft
.builder()
.name(LocalizedString.ofEnglish("name"))
.slug(LocalizedString.ofEnglish("slug"))
.externalId("externalId")
.metaTitle(LocalizedString.ofEnglish("metaTitle"))
.metaDescription(LocalizedString.ofEnglish("metaDescription"))
.metaKeywords(LocalizedString.ofEnglish("metaKeywords"))
.build();

Create resources

In the v2 SDK there are no dedicated classes for the create command, but there are builders instead. The Create Command needs a Draft that is built and passed to the post() method.

Example code from Java v1 SDKJava
LocalizedString name = LocalizedString.ofEnglish("name");
LocalizedString slug = LocalizedString.ofEnglish("slug");
CategoryDraft categoryDraft = CategoryDraftBuilder.of(name, slug).build();
Category category = blockingClient()
.executeBlocking(CategoryCreateCommand.of(categoryDraft));
Example code for Java v2 SDKJava
CategoryDraft categoryDraft = CategoryDraft
.builder()
.name(LocalizedString.ofEnglish("name"))
.slug(LocalizedString.ofEnglish("slug"))
.build();
Category category = projectClient()
.categories()
.post(categoryDraft)
.executeBlocking()
.getBody();

Create resources from JSON

In the v2 SDK, the JsonUtils class replaces the SphereJsonUtils class.

Example code from Java v1 SDKJava
final CategoryDraft categoryDraft = SphereJsonUtils.readObjectFromResource(
"category.json",
CategoryDraft.class
);
final Category category = blockingClient()
.executeBlocking(CategoryCreateCommand.of(categoryDraft));
Example code for Java v2 SDKJava
final CategoryDraft categoryDraft = JsonUtils.fromJsonString(
TestUtils.stringFromResource("category.json"),
CategoryDraft.class
);
final Category category = projectClient()
.categories()
.post(categoryDraft)
.executeBlocking()
.getBody();

Update resources

In the v2 SDK there are no dedicated classes for the update command. The UpdateBuilder is used to create the type of update action to apply in the post() method.

Example code from Java v1 SDKJava
LocalizedString newName = LocalizedString.ofEnglish("new name");
CategoryUpdateCommand command = CategoryUpdateCommand.of(
category,
Collections.singletonList(ChangeName.of(newName))
);
Category updatedCategory = blockingClient().executeBlocking(command);
Example code for Java v2 SDKJava
CategoryUpdate categoryUpdate = CategoryUpdate
.builder()
.version(category.getVersion())
.plusActions(actionBuilder ->
actionBuilder
.changeNameBuilder()
.name(LocalizedString.ofEnglish("new name"))
)
.build();
Category updatedCategory = projectClient()
.categories()
.withId(category.getId())
.post(categoryUpdate)
.executeBlocking()
.getBody();

For more examples of update actions in the v2 SDK, see the integration tests.

Get a resource by ID

Retrieving resources by ID in the v2 SDK requires .withId() before .get(), after which additional filters or requests (such as .withExpand()) can be included before the request is executed.

Example code from Java v1 SDKJava
Category loadedCategory = blockingClient()
.executeBlocking(
CategoryByIdGet.of(category.getId()).withExpansionPaths(m -> m.parent())
);
Example code for Java v2 SDKJava
Category queriedCategory = projectClient()
.categories()
.withId(category.getId())
.get()
.withExpand("parent")
.executeBlocking()
.getBody();

Query resources

To build complex queries, the v2 SDK uses ResourcePagedQueryResponse (as opposed to PagedQueryResult in the v1 SDK). ResourcePagedQueryResponse can apply the limit, count, total, offset, and result to the query.

Example code from Java v1 SDKJava
PagedQueryResult<Category> pagedQueryResult = blockingClient()
.executeBlocking(CategoryQuery.of().byId("id123"));
Example code for Java v2 SDKJava
CategoryPagedQueryResponse response = projectClient()
.categories()
.get()
.withWhere("id = :id", "id", "id123")
.executeBlocking()
.getBody();
// Using query DSL builder
CategoryPagedQueryResponse response = projectClient()
.categories()
.get()
.withWhere(CategoryQueryBuilderDsl.of().id().is("id123"))
.executeBlocking()
.getBody();

For more examples of Querydsl in the v2 SDK, see the query tests.

Accessing Attribute values

To access Attributes with the correct type and avoid type casting, use AttributeAccessor with the correct type.

Example code from Java v1 SDKJava
final String attributeValue = product
.getMasterData()
.getStaged()
.getMasterVariant()
.getAttribute("test")
.getValueAsString();
Example code for Java v2 SDKJava
final String attributeValue = product
.getMasterData()
.getStaged()
.getMasterVariant()
.getAttribute("products")
.withAttribute(AttributeAccessor::asString);

For more examples of using AttributeAccessor in the v2 SDK, see the Attribute tests.

Compatibility layer for Java v1 SDK

The Java v2 SDK introduced breaking changes to the SDK client configuration. The commercetools-sdk-compat-v1 compatibility layer module allows you to use the v2 SDK in conjunction with the v1 SDK, enabling you to simplify the migration process.

When you use the compatibility layer, the v1 SDK adapts its internal logic, so you don't need to change your whole business logic. Use this flexibility to gradually migrate to the v2 SDK.

Using Requests from the Java v1 SDK

The CompatClient can execute a SphereRequest with an ApiHttpClient.

import com.commercetools.api.defaultconfig.ApiRootBuilder;
import com.commercetools.api.models.project.Project;
import io.sphere.sdk.projects.queries.ProjectGet;
import io.vrap.rmf.base.client.ApiHttpClient;
import io.vrap.rmf.base.client.oauth2.ClientCredentials;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class CompatClientUsageTest {
@Test
public void compatClient() {
final ApiHttpClient apiHttpClient = ApiRootBuilder.of()
.defaultClient(ClientCredentials.of()
.withClientId(CommercetoolsTestUtils.getClientId())
.withClientSecret(CommercetoolsTestUtils.getClientSecret())
.build())
.buildClient();
final String projectKey = CommercetoolsTestUtils.getProjectKey();
CompatClient client = CompatClient.of(apiHttpClient, projectKey);
Project project = client.executeBlocking(ProjectGet.of(), Project.class).getBody();
Assertions.assertThat(project).isInstanceOf(Project.class);
Assertions.assertThat(project.getKey()).isEqualTo(projectKey);
}
}

Creating a SphereClient with the Java v2 SDK

The CompatSphereClient acts as a replacement SphereClient for applications using the v1 SDK.

public class CompatSphereClientUsageTest {
@Test
public void sphereCompatClient()
throws ExecutionException, InterruptedException {
SphereClientConfig clientConfig = SphereClientConfig.of(
CommercetoolsTestUtils.getProjectKey(),
CommercetoolsTestUtils.getClientId(),
CommercetoolsTestUtils.getClientSecret()
);
SphereClient client = CompatSphereClient.of(clientConfig);
Project project = client
.execute(ProjectGet.of())
.toCompletableFuture()
.get();
Assertions.assertThat(project).isInstanceOf(Project.class);
}
}