# Storage Blob Service SDK Migration Guide from 10.x/11.x to 12.x In this section, we list the main changes you need to be aware of when converting your Storage Blob SDK library from Version 10/Version 11 to Version 12. ## Prerequisites Java Development Kit (JDK) with version 8 or above ## Reactor and ReactorNetty We've updated our asynchronous framework from RxJava to Reactor. For more info related to Reactor, please refer to [Project Reactor](https://projectreactor.io/) For more info of the motivation behind this major change, please refer to [this guide](https://github.com/Azure/azure-storage-java/blob/master/V12%20Upgrade%20Story.md). ## Converting Core Classes Our core asynchronous classes have been replaced, as well as new synchronous counterparts added. | Core V10/11 classes | Equivalent V12 Class | NEW synchronous clients | |---:|---:|---:| | ServiceURL | BlobServiceAsyncClient | BlobServiceClient | | ContainerURL | ContainerAsyncClient | ContainerClient | | BlobURL | BlobAsyncClient | BlobClient | | BlockBlobURL | BlockBlobAsyncClient | BlockBlobClient | | AppendBlobURL | AppendBlobAsyncClient | AppendBlobClient | | PageBlobURL | PageBlobAsyncClient | PageBlobClient | **Note:** The methods under TransferManager in V10/V11 were moved to `BlobAsyncClient` and `BlobClient` in V12. ## Updated Maven dependency Dependency for Blob service: ```xml com.azure azure-storage-blob 12.0.0 ``` ## Build Client In V10/V11, core classes were built with constructors, taking a `URL` and an already constructed `HttpPipeline`. ```java final BlockBlobURL blobURL = new BlockBlobURL( new URL("https://" + "" + ".blob.core.windows.net/mycontainer/myimage.jpg"), StorageURL.createPipeline(new SharedKeyCredentials("", ""), new PipelineOptions())); ``` In V12, we have moved to a builder pattern. To replicate the above V10/V11 snippet: ```java // To get the BlobClient, we can use builder to initialize. final BlobClient blobClient = new BlobClientBuilder() .endpoint("https://" + "" + ".blob.core.windows.net") .containerName("mycontainer") .blobName("myimage.jpg") .credential(new StorageSharedKeyCredential("", "")) .buildClient(); // We can initialize BlockBlobClient from blobClient. final BlockBlobClient blockBlobClient = blobClient.getBlockBlobClient(); // we can also choose PageBlobClient, AppendBlobClient, and BlobClient ``` These builders can also build sync and async clients interchangably. Just select the correct "build" method. Additionally, if you already have the full URL, you can just specify that as the endpoint instead of naming container and blob individually, similar to V10/V11. ```java final BlobAsyncClient blobAsyncClient = new BlobClientBuilder() .endpoint("https://" + "" + ".blob.core.windows.net/mycontainer/myimage.jpg") .credential(new StorageSharedKeyCredential("", "")) .buildAsyncClient(); final BlockBlobAsyncClient blockBlobClient = blobAsyncClient.getBlockBlobAsyncClient(); ``` ## Generate SAS token SAS token generation has moved from helper classes to the core clients themselves. See section [SAS Token](#sastoken) to learn how to use them as a credential. Not all fields being set in these samples are necessary. At minimum, only an expiry time and permission set are necessary to create a SAS. In V10/V11, to generate the SAS token, you needed an instance of ServiceSASSignatureValues to fluently build out all it's options. This could then generate SASQueryParameters which contained the actual URL query string to attach to your URL. You also needed to provide container and blob names when necessary, as well as the `SharedKeyCredential` to sign the token with. ```java // specify token properties ServiceSASSignatureValues value = new ServiceSASSignatureValues(); BlobSASPermission p = new BlobSASPermission() .withRead(true) .withWrite(true) .withCreate(true) .withDelete(true) .withAdd(true); value.withPermissions(p.toString()) .withStartTime(OffsetDateTime.now().minusDays(1)) .withExpiryTime(OffsetDateTime.now().plusDays(1)) .withContainerName("") .withBlobName(""); IPRange ipR = new IPRange() .withIpMin("0.0.0.0") .withIpMax("255.255.255.255"); value.withIpRange(ipR) .withProtocol(SASProtocol.HTTPS_ONLY) .withCacheControl("cache") .withContentDisposition("disposition") .withContentEncoding("encoding") .withContentLanguage("language") .withContentType("type"); // build the token SASQueryParameters sasToken = value.generateSASQueryParameters(sharedKeyCredentails); ``` In V12, SAS tokens are generated off the client to the resource you wish to generate the SAS for. The resource path handled automatically because of this, and the `SharedKeyCredential` authenticating the client is also used automatically to sign the SAS. See [building a client](#buildclient) for how to get an authenticated client. ```java // specify token properties String identifier = "identifier"; BlobSASPermission permissions = new BlobSASPermission() .withRead(true) .withCreate(true) .withDelete(true) .withWrite(true); // We can also choose BlobContainerSasPermission, BlobServiceSasQueryParameters based on the object level we want to grant access. OffsetDateTime startTime = OffsetDateTime.now().minusDays(1); OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1); SasIpRange ipRange = new SasIpRange() .setIpMax("0.0.0.0") .setIpMax("255.255.255.255"); SasProtocol sasProtocol = SasProtocol.HTTPS_HTTP; String cacheControl = "cache"; String contentDisposition = "disposition"; String contentEncoding = "encoding"; String contentLanguage = "language"; String contentType = "type"; String version = BlobServiceVersion.V2019_02_02.getVersion(); // build the token BlobServiceSasSignatureValues sasSignatureValues = new BlobServiceSasSignatureValues(version, sasProtocol, startTime, expiryTime, permissions.toString(), ipRange, identifier, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); BlobServiceSasQueryParameters sasQueryParameters = sasSignatureValues.generateSasQueryParameters(new StorageSharedKeyCredential("", ""), StorageURL.createPipeline(credential, new PipelineOptions())); ``` Since the builder in V12 manages pipeline generation, you can just hand the `StorageSharedKeyCredential` to the builder. ```java // Initialize the credentials StorageSharedKeyCredential credential = new StorageSharedKeyCredential("${accountName}", "${accountKey}"); // How service object take in the credentials. BlobServiceClient client = new BlobServiceClientBuilder() .endpoint("https://" + "" + ".blob.core.windows.net") .credential(credential) .buildClient(); ``` ### SASToken A URL with a SAS token looks like the following: `https://${accountName}.blob.core.windows.net/?${sasToken}`. Refer the [documentation](https://github.com/Azure/azure-sdk-for-java/blob/azure-storage-file-share_12.0.0/sdk/storage/azure-storage-blob/README.md) for more info. See section [Generate SAS token](#generate-sas-token) for how to generate the token programmatically. In V10/V11, you had to provide the URL with the token attached, which often resulted in URL manipulation when a token could be used on more than one resource. ```java // Here is how the blob object take in the sas token generated in above section. ContainerURL containerURL = new ContainerURL(new URL(""), StorageURL.createPipeline(credential, new PipelineOptions())); BlobURLParts parts = URLParser.parse(containerURL.createBlobURL("").toURL()); parts.withSasQueryParameters(sasToken).withScheme("https"); BlockBlobURL blockBlobURL = new BlockBlobURL(parts.toURL(), StorageURL.createPipeline( new AnonymousCredentials(), new PipelineOptions())); ``` In V12, SAS tokens can be managed just like any other credential. ```java // How service object take in the credentials. BlobServiceClient client = new BlobServiceClientBuilder() .endpoint("https://${accountName}.blob.core.windows.net") .sasToken("${sasToken}") .buildClient(); ``` However, if you already have the full URL with SAS token attached, you can use that too: ```java BlobServiceClient client = new BlobServiceClientBuilder() .endpoint("https://${accountName}.blob.core.windows.net/?${sasToken}") .buildClient(); ``` ### Pipeline and pipeline options In V10/V11, pipeline options build into pipeline through StorageURL, e.g. `RequestRetryOptions`. ```java HttpPipeline pipeline = StorageURL.createPipeline(new SharedKeyCredentials("", ""), new PipelineOptions().withRequestRetryOptions(new RequestRetryOptions())); ``` In V12, it is flattened to just be on the builder. ```java BlobServiceClient client = new BlobServiceClientBuilder() .endpoint("https://${accountName}.blob.core.windows.net") .sasToken("${sasToken}") .retryOptions(new RequestRetryOptions()) .buildClient(); ``` ## Provide side by side sync-over-async APIs Each type (Container, Blob, etc.) has both a sync and async client. It is easy and intuitive to use whichever one is preferable in a given context without having to bother about clutter from the other. Suppose the Container Client been initialized as `${containerClient}` for sync and `${containerAsyncClient}` for async. Please refer the [Build Client](#build-client) section. * Example of using Async API in V12. ```java Mono response = containerAsyncClient.getProperties(); ``` * Example of using Sync API in V12 ```java BlobContainerProperties blobProperties = containerClient.getProperties(); ``` ## Async APIs Comparison In V10/V11, every RESTful API returned a reactive response of type `Single` (see RxJava documentation for more details), which emits one and only one result. These returned object representations of the single HTTP response that came back. Single result API in V10/V11: ```java serviceURL.getProperties(Context.NONE).subscribe( // print the selected service property on success response -> System.out.println(response.body().defaultServiceVersion()), // handle error error -> System.out.println(error.getMessage())); ``` Listing API in V10/V11: ```java serviceURL.listContainersSegment(null, null, Context.NONE).subscribe( // print the list; grab continuation token for making another call to get next list segment response -> { System.out.println("Page token to fetch more containers:" + response.body().nextMarker()); response.body().containerItems().stream().forEach( containerItem -> System.out.println("Container name: " + containerItem.name())); }, // handle error error -> System.out.println(error.getMessage())); ``` In V12, Reactor provides an equivalent class `Mono` (0-1 response). There is also the `Flux` class for zero-to-many responses, and Azure client libraries use a subclass known at `PagedFlux` to also provide pagination and response information. Additionally, classes returned from APIs are the "core information" of the response, rather than the HTTP response exactly. Full access to all fields of the HTTP response is available available by using the methods having the suffix -withResponse, but examples of that usage are beyond the scope Suppose the BlobServiceClient has been initialized as `serviceClient` and the BlobServiceAsyncClient has been initialized as `serviceAsyncClient`. Single result API in V12, similar to V10/V11: ```java serviceAsyncClient.getProperties().subscribe( // print the selected service property on success properties -> System.out.println(properties.getDefaultServiceVersion()), // handle error error -> System.out.println(error.getMessage()), // on completion () -> System.out.println("Get properties request completed successfully.")); ``` In V12, you no longer need to worry about paginated results. Listing API in V12: ```java serviceAsyncClient.listBlobContainers() // print each item in the list .doOnNext(containerItem -> System.out.println(containerItem.getName())) // handle error .doOnError(error -> System.out.println(error.getMessage())) // on completion .doFinally(sig -> System.out.println("Paginated listing request completed successfully.")) .subscribe(); ``` More on paginated requests in [this section](#pagination). ## Minimum Overload APIs and Maxmum Overload APIs: In V10/V11, we only provide one maximum overload API for each operation. ```java public Single create(Metadata metadata, PublicAccessType accessType, Context context) ``` In V12, we provide at least one minimum and one maximum for most of the operations. Minimum overload in async: ```java public Mono getProperties() ``` Maximum overload in async: ```java public Mono> getPropertiesWithResponse(String leaseId) ``` ## Pagination: Listing or Paging API returned `Single` in V10/V11, where `SomeResponseType` contained a List. You had to manually follow a continuation token if provided List all blobs V8: ```java private ContainerURL containerURL; private List retrieveBlobsForCurrentPage(String pageToken, List results) { if (pageToken == null) { return results; } containerURL.listBlobsFlatSegment(pageToken, null, Context.NONE).subscribe( response -> { results.addAll((Collection) response.body().segment().blobItems()); retrieveBlobsForCurrentPage(response.body().nextMarker(), results); } ); return results; } public void run() throws Exception{ // Build ContainerURL containerURL = new ContainerURL(new URL("https://" + "" + ".blob.core.windows.net"), StorageURL.createPipeline(new SharedKeyCredentials("", ""), new PipelineOptions())); // Initialize empty blob list List results = new ArrayList<>(); // Start from first page containerURL.listBlobsFlatSegment(null, null, Context.NONE).subscribe( response -> { results.addAll((Collection) response.body().segment().blobItems()); if (response.body().nextMarker() != null) { results.addAll((Collection) retrieveBlobsForCurrentPage(response.body().nextMarker(), results)); } }); } ``` V12 provides two pagination classes: `PagedIterable` for sync and `PageFlux` for async. These allow you to consume listing operations by individual item or by response pages. The latter is needed to access general information in each HTTP response. List all blobs using async API V12, by page: ```java private BlobContainerAsyncClient containerAsyncClient; private List retrieveBlobs(String pageToken, List results) { if (pageToken == null) { return results; } containerAsyncClient.listBlobs().byPage(pageToken).subscribe( response -> { results.addAll((Collection) response.getValue()); retrieveBlobs(response.getContinuationToken(), results); } ); return results; } public void run() { // Build ContainerURL containerAsyncClient = new BlobContainerClientBuilder().credential(new StorageSharedKeyCredential("", "")).buildAsyncClient(); // Initialize empty blob list List results = new ArrayList<>(); // Start from first page containerAsyncClient.listBlobs() .byPage() .subscribe(response -> { results.addAll((Collection) response.getValue()); if (response.getContinuationToken() != null) { results.addAll((Collection) retrieveBlobs(response.getContinuationToken(), results)); } }); } ```