Azure Functions Java developer guide

Note

Java for Azure Functions is currently in preview. To receive important updates, including breaking changes announcements, watch the Azure App Service announcements repository.

Programming model

The concepts of triggers and bindings are fundamental to Azure Functions. Triggers start the execution of your code. Bindings give you a way to pass data to and return data from a function, without having to write custom data access code.

A function should be a stateless method to process input and produce output. Your function should not depend on any instance fields of the class. All the function methods should be public and method with annotation @FunctionName must be unique as method name defines the entry for a function.

Folder structure

Here is the folder structure of an Azure Function Java project:

FunctionsProject
 | - src
 | | - main
 | | | - java
 | | | | - FunctionApp
 | | | | | - MyFirstFunction.java
 | | | | | - MySecondFunction.java
 | - target
 | | - azure-functions
 | | | - FunctionApp
 | | | | - FunctionApp.jar
 | | | | - host.json
 | | | | - MyFirstFunction
 | | | | | - function.json
 | | | | - MySecondFunction
 | | | | | - function.json
 | | | | - bin
 | | | | - lib
 | - pom.xml

There's a shared host.json file that can be used to configure the function app. Each function has its own code file (.java) and binding configuration file (function.json).

You can put more than one function in a project. Avoid putting your functions into separate jars. The FunctionApp in the target directory is what gets deployed to your function app in Azure.

Triggers and annotations

Azure functions are invoked by a trigger, such as an HTTP request, a timer, or an update to data. Your function needs to process that trigger and any other inputs to produce one or more outputs.

Use the Java annotations included in the com.microsoft.azure.functions.annotation.* package to bind input and outputs to your methods. For more information see Java reference docs.

Important

You must configure an Azure Storage account in your local.settings.json to run Azure Storage Blob, Queue, or Table triggers locally.

Example:

public class Function {
    public String echo(@HttpTrigger(name = "req", 
      methods = {"post"},  authLevel = AuthorizationLevel.ANONYMOUS) 
        String req, ExecutionContext context) {
        return String.format(req);
    }
}

here is the generated corresponding function.json by the azure-functions-maven-plugin:

{
  "scriptFile": "azure-functions-example.jar",
  "entryPoint": "com.example.Function.echo",
  "bindings": [
    {
      "type": "httpTrigger",
      "name": "req",
      "direction": "in",
      "authLevel": "anonymous",
      "methods": [ "post" ]
    },
    {
      "type": "http",
      "name": "$return",
      "direction": "out"
    }
  ]
}

JDK runtime availability and support

Download and use the Azul Zulu for Azure JDKs from Azul Systems for local development of Java function apps. JDKs are available for Windows, Linux, and macOS. Azure support is available with a qualified support plan.

Third-party libraries

Azure Functions supports the use of third-party libraries. By default, all dependencies specified in your project pom.xml file will be automatically bundled during the mvn package goal. For libraries not specified as dependencies in the pom.xml file, place them in a lib directory in the function's root directory. Dependencies placed in the lib directory will be added to the system class loader at runtime.

The com.microsoft.azure.functions:azure-functions-java-library dependency is provided on the classpath by default, and does not need to be included in the lib directory. Also, dependencies listed here are added to the classpath by azure-functions-java-worker.

Data type support

You can use Plain old Java objects (POJOs), types defined in azure-functions-java-library or primitive dataTypes such as String, Integer to bind to input/output bindings.

Plain old Java objects (POJOs)

For converting input data to POJO, azure-functions-java-worker uses gson library. POJO types used as inputs to functions should be public.

Binary data

Bind binary inputs or outputs to byte[] by setting the dataType field in your function.json to binary:

   @FunctionName("BlobTrigger")
    @StorageAccount("AzureWebJobsStorage")
     public void blobTrigger(
        @BlobTrigger(name = "content", path = "myblob/{fileName}", dataType = "binary") byte[] content,
        @BindingName("fileName") String fileName,
        final ExecutionContext context
    ) {
        context.getLogger().info("Java Blob trigger function processed a blob.\n Name: " + fileName + "\n Size: " + content.length + " Bytes");
    }

Use Optional<T> for if null values are expected

Bindings

Input and output bindings provide a declarative way to connect to data from within your code. A function can have multiple input and output bindings.

Example Input binding

package com.example;

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

public class Function {
    @FunctionName("echo")
    public static String echo(
        @HttpTrigger(name = "req", methods = { "put" }, authLevel = AuthorizationLevel.ANONYMOUS, route = "items/{id}") String inputReq,
        @TableInput(name = "item", tableName = "items", partitionKey = "Example", rowKey = "{id}", connection = "AzureWebJobsStorage") TestInputData inputData
        @TableOutput(name = "myOutputTable", tableName = "Person", connection = "AzureWebJobsStorage") OutputBinding<Person> testOutputData,
    ) {
        testOutputData.setValue(new Person(httpbody + "Partition", httpbody + "Row", httpbody + "Name"));
        return "Hello, " + inputReq + " and " + inputData.getKey() + ".";
    }

    public static class TestInputData {
        public String getKey() { return this.RowKey; }
        private String RowKey;
    }
    public static class Person {
        public String PartitionKey;
        public String RowKey;
        public String Name;

        public Person(String p, String r, String n) {
            this.PartitionKey = p;
            this.RowKey = r;
            this.Name = n;
        }
    }
}

This function is invoked with an HTTP request.

  • HTTP request payload is passed as a String for the argument inputReq
  • One entry is retrieved from the Azure Table Storage and is passed as TestInputData to the argument inputData.

To receive a batch of inputs, you can bind to String[], POJO[], List<String> or List<POJO>.

@FunctionName("ProcessIotMessages")
    public void processIotMessages(
        @EventHubTrigger(name = "message", eventHubName = "%AzureWebJobsEventHubPath%", connection = "AzureWebJobsEventHubSender", cardinality = Cardinality.MANY) List<TestEventData> messages,
        final ExecutionContext context)
    {
        context.getLogger().info("Java Event Hub trigger received messages. Batch size: " + messages.size());
    }

    public class TestEventData {
    public String id;
}

This function gets triggered whenever there is new data in the configured event hub. As the cardinality is set to MANY, function receives a batch of messages from event hub. EventData from event hub gets converted to TestEventData for the function execution.

Example Output binding

You can bind an output binding to the return value using $return

package com.example;

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

public class Function {
    @FunctionName("copy")
    @StorageAccount("AzureWebJobsStorage")
    @BlobOutput(name = "$return", path = "samples-output-java/{name}")
    public static String copy(@BlobTrigger(name = "blob", path = "samples-input-java/{name}") String content) {
        return content;
    }
}

If there are multiple output bindings, use the return value for only one of them.

To send multiple output values, use OutputBinding<T> defined in the azure-functions-java-library package.

@FunctionName("QueueOutputPOJOList")
    public HttpResponseMessage QueueOutputPOJOList(@HttpTrigger(name = "req", methods = { HttpMethod.GET,
            HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
            @QueueOutput(name = "itemsOut", queueName = "test-output-java-pojo", connection = "AzureWebJobsStorage") OutputBinding<List<TestData>> itemsOut, 
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");

        String query = request.getQueryParameters().get("queueMessageId");
        String queueMessageId = request.getBody().orElse(query);
        itemsOut.setValue(new ArrayList<TestData>());
        if (queueMessageId != null) {
            TestData testData1 = new TestData();
            testData1.id = "msg1"+queueMessageId;
            TestData testData2 = new TestData();
            testData2.id = "msg2"+queueMessageId;

            itemsOut.getValue().add(testData1);
            itemsOut.getValue().add(testData2);

            return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + queueMessageId).build();
        } else {
            return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Did not find expected items in CosmosDB input list").build();
        }
    }

     public static class TestData {
        public String id;
    }

Above function is invoked on an HttpRequest and writes multiple values to the Azure Queue

HttpRequestMessage and HttpResponseMessage

HttpRequestMessage and HttpResponseMessage types are defined in azure-functions-java-library are helper types to work with HttpTrigger functions

Specialized Type Target Typical Usage
HttpRequestMessage<T> HTTP Trigger Get method, headers, or queries
HttpResponseMessage HTTP Output Binding Return status other than 200

Metadata

Few triggers send trigger metadata along with input data. You can use annotation @BindingName to bind to trigger metadata

package com.example;

import java.util.Optional;
import com.microsoft.azure.functions.annotation.*;


public class Function {
    @FunctionName("metadata")
    public static String metadata(
        @HttpTrigger(name = "req", methods = { "get", "post" }, authLevel = AuthorizationLevel.ANONYMOUS) Optional<String> body,
        @BindingName("name") String queryValue
    ) {
        return body.orElse(queryValue);
    }
}

In the example above, the queryValue is bound to query string parameter name in the Http request URL http://{example.host}/api/metadata?name=test. Following is another example to binding to Id from queue trigger metadata

 @FunctionName("QueueTriggerMetadata")
    public void QueueTriggerMetadata(
        @QueueTrigger(name = "message", queueName = "test-input-java-metadata", connection = "AzureWebJobsStorage") String message,@BindingName("Id") String metadataId,
        @QueueOutput(name = "output", queueName = "test-output-java-metadata", connection = "AzureWebJobsStorage") OutputBinding<TestData> output,
        final ExecutionContext context
    ) {
        context.getLogger().info("Java Queue trigger function processed a message: " + message + " whith metadaId:" + metadataId );
        TestData testData = new TestData();
        testData.id = metadataId;
        output.setValue(testData);
    }

Note

Name provided in the annotation needs to match the metadata property

Execution context

ExecutionContext defined in the azure-functions-java-library contains helper methods to communicate with the functions runtime.

Logger

Use getLogger defined in ExecutionContext to write logs from function code.

Example:


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

public class Function {
    public String echo(@HttpTrigger(name = "req", methods = {"post"}, authLevel = AuthorizationLevel.ANONYMOUS) String req, ExecutionContext context) {
        if (req.isEmpty()) {
            context.getLogger().warning("Empty request body received by function " + context.getFunctionName() + " with invocation " + context.getInvocationId());
        }
        return String.format(req);
    }
}

View logs and trace

You can use the Azure CLI to stream Java stdout and stderr logging as well as other application logging.

Configure your Function application to write application logging using the Azure CLI:

az webapp log config --name functionname --resource-group myResourceGroup --application-logging true

To stream logging output for your Function app using the Azure CLI, open a new command prompt, Bash, or Terminal session and enter the following command:

az webapp log tail --name webappname --resource-group myResourceGroup

The az webapp log tail command has options to filter output using the --provider option.

To download the log files as a single ZIP file using the Azure CLI, open a new command prompt, Bash, or Terminal session and enter the following command:

az webapp log download --resource-group resourcegroupname --name functionappname

You must have enabled file system logging in the Azure Portal or Azure CLI before running this command.

Environment variables

In Functions, app settings, such as service connection strings, are exposed as environment variables during execution. You can access these settings using, System.getenv("AzureWebJobsStorage")

Example:

Add AppSetting with name testAppSetting and value testAppSettingValue


public class Function {
    public String echo(@HttpTrigger(name = "req", methods = {"post"}, authLevel = AuthorizationLevel.ANONYMOUS) String req, ExecutionContext context) {
        context.getLogger().info("testAppSetting "+ System.getenv("testAppSettingValue"));
        return String.format(req);
    }
}

Next steps

For more information about Azure Function Java development, see the following resources: