7 min read

Things to Know When Writing Azure Functions in Java

Justin Yoo

Azure Functions doesn't only support C# but also supports many other languages, including Java, JavaScript, Python, PowerShell and so forth. Specifically, I'm going to discuss a few things to know when you're developing a Java-based Azure Functions app, especially on Mac.

Local Development Environments

It's not that a big issue when using either Visual Studio or Visual Studio Code for .NET function app development. In addition to that, it's not that a big issue when using Visual Studio Code for JavaScript-based function app development, either.

Basic reference docs for Java devs to develop Azure Functions app, please refer to this Azure Functions Java Developers' Guide page.

It's common that the majority of Java devs use IntelliJ IDEA (IntelliJ). I believe that Java Function app development also uses IntelliJ. But if you're on Mac, you should know the following Mac-specific issues so that you don't get hiccups.

Things to Know When Using IntelliJ

  1. IntelliJ Edition:

    There's no dependency on the Spring framework for Azure Functions app development. Therefore, you don't need the Ultimate Edition for Function app development. Of course, if you want to utilise many other features that UE only supports, you should use UE. But basically, CE would be sufficient for Function app development.

    Comparison between IntelliJ UE and CE

    It's also better to install the Azure Tools for IntelliJ plug-in for the Function app development because it's bootstrapping and templating all the Function app related stuff.

    Azure Tools for IntelliJ

    The doc page, Building the First Java Function App with IntelliJ, provides essential information to build a Java function app and deploy it to Azure.

  2. Maven Build:

    When you write an Azure Functions app with IntelliJ, it has the dependency on the Maven build. So, if you want to integrate with the Gradle build, follow this page, Building an Azure Functions app with Gradle, and import it to IntelliJ.

Things to Know When Using IntelliJ on Mac OS

Let's focus on the "Azure Functions app development". How IntelliJ works on Mac is different from doing on Windows in that context. The following two issues are only happening on Mac.

  1. Azure Functions Core Tools Loading:

    If you install Azure Functions Core Tools on Windows, open IntelliJ and run the Azure Functions app locally, it works with no issue.

    Run Azure Functions app via IntelliJ on Windows

    However, if you install Azure Functions Core Tools on Mac, open IntelliJ from Finder or Dock, and run the Azure Functions app locally, it throws the error like below – it doesn't recognise Azure Functions Core Tools.

    Run Azure Functions app via IntelliJ on Mac via Finder

    By the way, you can open IntelliJ from Terminal by typing the idea . command.

    Open IntelliJ from Terminal

    With this IntelliJ instance, run the Azure Functions app locally. Then, it recognises Azure Functions Core Tools and runs the app with no issue.

    Run Azure Functions app via IntelliJ on Mac

    Why does this discrepancy occur? It's because the loaded environment variables are different between "Finder-launched application" and "Shell-launched application". Due to this difference, JVM is loaded differently. This issue has already been addressed.

    When you see the $PATH variable from the "Finder-launched IntelliJ", it looks like this:

    $PATH value from Finder-launched IntelliJ

    Let's check the same $PATH variable from the "Shell-launched IntelliJ". Can you see the difference?

    $PATH value from Shell-launched IntelliJ

    It's not fixed yet. It's not even sure whether the issue should go to the plugin or IntelliJ. Therefore, the only workaround, for now, is that you should open the IntelliJ instance from the command line.

  2. Azure Functions Port Lockout:

    You run the Azure Functions App within IntelliJ, stop running the app, and re-run the app. Then, you will see the following error – the port 7071 is still being used.

    Port is still being used

    Azure Functions uses two processes – main process and sub-process. The Azure Functions runtime runs on the main process, and the language worker runs on the sub-process. In the shell environment or Visual Studio Code, all the child processes are also terminated when the main process is terminated. On the other hand, IntelliJ only kills the main process, not the child process, which occupies the existing port.

    In this case, you have to manually find the process holding the port number and kill it. Here are the sample bash commands.

    process_id=$(sudo lsof -nP -i4TCP:7071 | grep LISTEN | awk '{print $2}')
    sudo kill -9 $process_id

    Alternatively, you can download the following shell script and run it when necessary.

    #!/bin/bash
    # Even after IntelliJ IDEA stops running the function app,
    # the process is still alive. It only happens on IntelliJ IDEA on Mac.
    # This script finds the Azure Functions process and kill it.
    #
    # You don't need this script if you use terminal or Visual Studio Code.
    # You don't need this script if you run IntelliJ IDEA on Windows.
    #
    # Usage: ./kill-process.sh -p 7071
    #
    set -e
    # Usage function
    function usage() {
    cat <<USAGE
    Usage: $0 [-p|--port-number <port number>] [-h|--help]
    Options:
    -p|--port-number: The port number to kill.
    Default: 7071
    -h|--help: Show this message.
    USAGE
    exit 1
    }
    # Set up arguments
    port_number=7071
    if [[ $# -eq 0 ]]; then
    port_number=7071
    fi
    while [[ "$1" != "" ]]; do
    case $1 in
    -p | --port-number)
    shift
    port_number=$1
    ;;
    -h | --help)
    usage
    exit 1
    ;;
    *)
    usage
    exit 1
    ;;
    esac
    shift
    done
    if [[ $port_number == "" ]]; then
    echo "Port number not set"
    usage
    exit 1
    fi
    echo "[$(date +"%Y-%m-%d %H:%M:%S")] Killing the process currently holding the port ..."
    process_id=$(sudo lsof -nP -i4TCP:$port_number | grep LISTEN | awk '{print $2}')
    if [[ $process_id != "" ]]; then
    echo "[$(date +"%Y-%m-%d %H:%M:%S")] Process ID: $process_id being killed ..."
    sudo kill -9 $process_id
    echo "[$(date +"%Y-%m-%d %H:%M:%S")] Process ID: $process_id has been killed. You can now run the app."
    else
    echo "[$(date +"%Y-%m-%d %H:%M:%S")] No process found to kill. You can now run the app."
    fi
    view raw kill-process.sh hosted with ❤ by GitHub

    As I've also piled an issue on the plugin repository, I'll update it here how it's going.

Azure Deployment Environment

According to the doc, Azure Functions supports both Java 8 and 11. Therefore, you need to configure the pom.xml file with the value of 1.8 or 11 for the java.version property and the value of 8 or 11 for the javaVersion property (line #6-7, 18-19, 32).

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<properties>
...
<java.version>1.8</java.version>
<javaVersion>8</javaVersion>
...
</properties>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>${azure.functions.maven.plugin.version}</version>
<configuration>
...
<runtime>
<!-- runtime os, could be windows, linux or docker-->
<os>windows</os>
<javaVersion>${javaVersion}</javaVersion>
...
</runtime>
...
</plugin>
...
</plugins>
</build>
</project>
view raw 02-pom.xml hosted with ❤ by GitHub

Here's a catch. If you provision the Azure Functions app instance through Azure Portal, you can choose the Java version.

Choosing Java Version on Azure Portal

However, you MUST explicitly declare the Java version when you provision the instance through bicep or ARM template; otherwise it is set to Java 8 by default. Therefore, if you want to deploy the function app with Java 11, this has to be declared in the bicep file to avoid confusion.

Here's the bicep sample. For the Linux plan, you should declare the linuxFxVersion to Java|11 (line #8). For the Windows plan you should declare the javaVersion to 11 (line #11).

resource <symbolic-name> 'Microsoft.Web/sites@2021-02-01' = {
...
properties: {
...
siteConfig: {
...
// Linux plan only
linuxFxVersion = 'Java|11'
// Windows Plan only
javaVersion = '11'
}
}
}

With the higher Java version (ie. Java 11) declared, you can deploy the app compiled in a lower version (ie. Java 8) and run it with no issue. But with the lower version (ie. Java 8) declared, your app compiled in the higher version (ie. Java 11) is deployed but will never work.


So far, we've discussed how to write an Azure Functions app in Java, what possible issues you should know if you're developing it on Mac, and what you should know when you deploy the app to Azure. While developing the app, I guess you will face other issues, but these issues and workarounds identified in this post will be the bare minimum points you should know.