Working with Azure Storage Blobs with a Java Azure Function
RSS feed Back to Blog
June 09, 2020
Tags: azure, java
If you’ve spent much time around me, you know that I’m a huge fan of Azure Functions. Being able to code stand-alone, serverless functions to handle tasks is an extremely powerful addition to any application. While my preference is C#, Azure Functions supports a number of other languages, allowing developers of all flavors to use the platform. In this article, I’ll show you how to create a simple Java Azure Function to work with Azure Storage files.

In my role as a Microsoft Cloud Solution Architect, I work with a lot of different companies, all with different skillsets and development stacks. While my forte is certainly the .NET stack, most companies use a blend of platforms to build their solutions. With a lineage back to 1991, Java has one of the largest followings, due to its immense versatility and capability. To help me work with clients who specialize in Java, I decided to create an Azure Function using Java to work with Azure Storage blobs.

NOTE

This blog is in no way an “expert” level article on Java programming. I’m just sharing some stuff I learned to hopefully help the next C# dev find their way.

Setup

To get going, I add Java support to Visual Studio Code. Now, there are a lot of IDE’s available for Java, and IntelliJ is probably one of the best. Just for fun, I wanted to see VS Code could do. Luckily, there’s a collection of extensions you can install to get up and going with Java quickly: The Java Extension Pack.



This collection contains several components you will need to develop Java apps with VS Code. Specifically, it includes Maven, which VS Code uses for project management (adding packages, etc.).

Azure Function Extension

Because I love Azure Functions, I also decided to add a Java Azure Function to the mix. Spoiler Alert: VS Code has a dedicated extension for Azure Functions which allows you to create new functions quickly.



Create a Java Azure Function Project

With the pre-reqs installed, I’m ready to create my new function. The VS Code extension walks through the process, selecting the language, codenames, and other details, depending on the type of function being created. For Java projects, VS Code only generates a sample HTTP Trigger function with the necessary Java components.



For simplicity, I delete the FunctionTest.java and test files.



If you’re looking for help creating C# functions with VS Code, check out this tutorial for a step-by-step.

Develop Azure Functions by using Visual Studio Code

Add pom.xml Dependencies

Before I add my Azure Storage code, I need to be able to access my storage accounts. Because packages are managed by Maven in VS Code, I need to add the Azure Storage dependency to my pom.xml file.

I open my pom.xml file and under the JAVA DEPENDENCIES tab, I select Maven Dependencies and click the +. In the search bar, I search for azure-storage and select the com.microsoft.azure option.



Selecting this dependency updates the pom.xml file with the new dependency declaration.



Add local.settings.json settings

Because I’m working with Azure resources, I will have some connection strings and other values I want to use in my code. By default, local.settings.json is ignored by git, so this is great location to store them (because I'm only running this function locally). Once deployed Azure, storing these values in the Application Settings or Azure Key Vault would keep them secure. 

I add the following values to the local.settings.json file. Most of these settings are for my Azure Function to record AppInsights and other data.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=XXXXXXXXXXXXXXXXXXXXXXXXXXX;AccountKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;EndpointSuffix=core.windows.net",
    "FUNCTIONS_WORKER_RUNTIME": "java",
    "FUNCTIONS_EXTENSION_VERSION": "~3",
    "APPINSIGHTS_INSTRUMENTATIONKEY": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "DefaultEndpointsProtocol=https;AccountName=XXXXXXXXXXXXXXXXXXXXXXX;AccountKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEndpointSuffix=core.windows.net",
    "WEBSITE_CONTENTSHARE": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    "AzureStorageDemoConnectionStringDest": "DefaultEndpointsProtocol=https;AccountName=bsoltisazurestoragedemo2;AccountKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEndpointSuffix=core.windows.net"
  },
  "ConnectionStrings": {
    "AzureStorageDemoConnectionStringSrc": "DefaultEndpointsProtocol=https;AccountName=bsoltisazurestoragedemo;AccountKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEndpointSuffix=core.windows.net"
  }
}

NOTE

My file contains a connection string for my “source” storage account, and a key/value pair for “destination” storage account. The function can pull from the ConnectionStrings section in run method, but I couldn’t figure out how to pull another connection string later in my code. To get around this, I put the 2nd key as a standard kvp. Again, I’m no Java expert so I’m sure there is way to do this.

Here’s my updated local.settings.json file.



Create a new function

Speaking of my function, it’s time to create it. I add a new file in the main\java\com\function folder to project with my new function name.



Next, I add the following import statements to reference my packages.

import java.io.IOException;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;

import com.microsoft.azure.functions.annotation.*;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.storage.*;
import com.microsoft.azure.storage.blob.*;


Next, I add the function code. I started with some base code from the following article, to get the correct run function parameters.

Azure Blob storage trigger for Azure Functions

   @FunctionName("AzureStorageDemoFunction")
   public void run(
         @BlobTrigger(name = "file", dataType = "binary", path = "files/{name}", connection = "AzureStorageDemoConnectionStringSrc") byte[] content,
         @BindingName("name") String filename, final ExecutionContext context)
         throws InvalidKeyException, URISyntaxException, StorageException {

	// Insert custom code
}


I am referencing the AzureStorageDemoConnectionStringSrc value from my local.settings.json file in the run method. I’m also setting the path (container name) to the location that I want to watch for new blobs (/files).
 

Next, I add in some standard code to access the "destination” storage account, create the new files2 container (if needed), and upload the blob as a new file.

      CloudStorageAccount storageAccountDest;
      CloudBlobClient blobClientDest = null;
      CloudBlobContainer containerDest = null;
      
      String storageConnectionStringDest = System.getenv("AzureStorageDemoConnectionStringDest");

      // Parse the connection string and create a blob client to interact with Blob
      // storage
      storageAccountDest = CloudStorageAccount.parse(storageConnectionStringDest);
      blobClientDest = storageAccountDest.createCloudBlobClient();
      containerDest = blobClientDest.getContainerReference("files2");

      // Create the container if it does not exist with public access.
      context.getLogger().info("Creating container: " + containerDest.getName());
      containerDest.createIfNotExists(BlobContainerPublicAccessType.CONTAINER, new BlobRequestOptions(),
            new OperationContext());

      CloudBlob blobDest = containerDest.getBlockBlobReference(filename);
      try {
         
         context.getLogger().info("Start Uploading blob: " + filename);
         blobDest.uploadFromByteArray(content, 0, content.length);
         context.getLogger().info("Finished Uploading blob: " + filename);

      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }


In this code, I’m using System.getenv(“AzureStorageDemoConnectionStringDest”) to get the new storage account. This pulls the value from the Key/Value Pair section, rather than the ConnectionStrings section of the local.settings.json file.


Here is the full function code, to help you see the complete logic. I am using context.getLogger().info() to log progress throughout the function execution.

   @FunctionName("AzureStorageDemoFunction")
   public void run(
         @BlobTrigger(name = "file", dataType = "binary", path = "files/{name}", connection = "AzureStorageDemoConnectionStringSrc") byte[] content,
         @BindingName("name") String filename, final ExecutionContext context)
         throws InvalidKeyException, URISyntaxException, StorageException {
      context.getLogger().info("Name: " + filename + " Size: " + content.length + " bytes");

      CloudStorageAccount storageAccountDest;
      CloudBlobClient blobClientDest = null;
      CloudBlobContainer containerDest = null;
      
      String storageConnectionStringDest = System.getenv("AzureStorageDemoConnectionStringDest");

      // Parse the connection string and create a blob client to interact with Blob
      // storage
      storageAccountDest = CloudStorageAccount.parse(storageConnectionStringDest);
      blobClientDest = storageAccountDest.createCloudBlobClient();
      containerDest = blobClientDest.getContainerReference("files2");

      // Create the container if it does not exist with public access.
      context.getLogger().info("Creating container: " + containerDest.getName());
      containerDest.createIfNotExists(BlobContainerPublicAccessType.CONTAINER, new BlobRequestOptions(),
            new OperationContext());

      CloudBlob blobDest = containerDest.getBlockBlobReference(filename);
      try {
         
         context.getLogger().info("Start Uploading blob: " + filename);
         blobDest.uploadFromByteArray(content, 0, content.length);
         context.getLogger().info("Finished Uploading blob: " + filename);

      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
    }


Testing

With the function created, I’m ready to do some testing. First, I run my function to initiate the monitoring of my “source” storage account. In the Terminal, you can see where it is listening on my localhost.



Next, I open my “source” and “destination” storage accounts. Note that both containers are currently empty.



In my “source” storage account, I upload the DM.jpg file to the files container.



In the Terminal in VS Code, I confirm the function executes when the file is uploaded. Note the “Start uploading blob: DM.jpg” and “Finished Uploading blob: DM.jpg” messages. This tells me the function recognized a new file had been uploaded to the “source” and copied top the “destination”.



Lastly, I refresh my storage accounts and confirm the DM.jpg file has been copied to the files2 container in my “destination” storage account.



Moving forward

As a long-time C# developer, working with Java is a big change. It’s a completely new way to package files, include dependencies, and host applications. Luckily, the code syntax is very similar, so I’m not completely in the dark. Hopefully, this blog helps anyone looking to make a similar transition and work with Azure Functions using multiple languages. Until next time!

Get the Code

You can get the full Azure Function code here:

View the Java Azure Function Azure Storage Sync project on GitHub