Running Kotlin in Azure Functions
Edit on GitHubA while back, the Azure folks announced support for Java on Azure Functions. My immediate thought was: “Do they mean Java or JVM? And if they mean JVM, will it work with Kotlin?” In this blog post, we’ll find out!
(and if you’re a .NET developer, you’ll learn a bit about that other platform: the JVM)
What are Azure Functions?
Azure Functions are Microsoft Azure’s event-driven, serverless compute experience. That’s all the buzzwords, probably, but it boils down to not having to worry about virtual machines, sites, …
The idea is we can just write a function, tell Azure we want to run it whenever an HTTP request comes in, a file is uploaded in a storage account, or when there is a full moon. Azure will execute it, no matter if it’s just once or 500.000 times.
Why try Azure Functions with Kotlin?
Over the past year, I was introduced to Kotlin at JetBrains. Some of our internal tools run it, IDE’s like Rider are built using it and I’ve been writing some plugins for our .NET IDE as well. One to run XDT transforms, one to manage .NET Core SDK versions and one that will provide C# Interactive support (but won’t live as a plugin but will be in the product out of the box).
To me, the language is easy to get started with coming from C#, in fact it feels like it combined the best things from C# with some F#, Swing, TypeScript, … - in short: it’s enjoyable! Kotlin compiles to the JVM and provides Java interop. What I find very neat is that it provides a C# LINQ-like syntax but compiles it to good-old-and-fast low-level language constructs like for
loops. In short: I’m starting to really really like this language!
Also loving Azure as a cloud platform, I felt like I had to marry these two. Turns out that marriage is quite easy if you know how to do it - which I did not but learned along the way. Here goes!
Prerequisites
In this blog post, I will use IntelliJ IDEA as the IDE. Next to that one, we’ll need JDK version 1.8 installed (and the JAVA_HOME
environment variable set).
For the Azure Functions side, we’ll need:
- .NET Core, latest version
- Azure CLI
- Node.js, version 8.6 or higher
- Azure Core Tools 2.0 - install them using npm:
npm install -g azure-functions-core-tools@core
Ready? Then let’s start!
1. Creating a new Azure Functions project in IntelliJ IDEA
The Azure folks have created a Maven Archetype which we can use. If you’re familiar with .NET Core, a Maven Archetype is like a dotnet new
template we can use to bootstrap a project. When creating a new project in IntelliJ IDEA, we can pick Maven as the project type, then Create from archetype, then pick (or add, first) the com.microsoft.azure/azure-functions-archetype
archetype version 1.0:
Next, we’ll have to specify a group id and artifact name (compared to .NET, these two are our NuGet package identifier - everything in Maven is a package so our project will be, too). I’ll go with the Java-style group be.maartenballiauw.azure
, then ktfunction
as the artifact name.
Next, we must add two additional properties: package
and appName
. These will be used later on by the Azure Functions Maven plugin to deploy our app. For package
, I’ll go with be.maartenballiauw.azure.ktfunction
and the appName
will be whatever our target function app is called in the Azure portal.
In the next step we can choose where on disk we want to save our project, then Finish and let IntelliJ IDEA create the new Azure functions project for us.
One more thing: since we’ll use Maven as the project system, IntelliJ IDEA will ask us to manually import the Maven project or auto-import it. Let’s Enable Auto-Import here.
Note: if you prefer command-line for the above steps, check the Azure Functions with Maven tutorial Microsoft created.
2. Enabling Kotlin in our project
We now have an Azure Functions project that uses Java. Let’s enable Kotlin! This requires either a few manual steps fiddling in our Maven pom.xml
file, or a few clicks in the IDE. Let’s go for the latter and use the Tools | Kotlin | Configure Kotlin in Project… menu. A dialog will ask us to specify which modules we want to enable it in, let’s just keep the defaults and make it happen.
We’re now ready to write some Kotlin!
3. Creating a first Kotlin Azure Function
If we look at the project structure, we already have two Java classes in our project:
src/main/java/be.maartenballiauw.azure.ktfunction/Function
- a simple “Hello World” added by the Azure Functions archetypesrc/test/java/be.maartenballiauw.azure.ktfunction/Function
- a test for the above class
We can do two things now:
- Open both classes in the editor, press Ctrl+Shift+Alt+K to let IntelliJ IDEA convert these classes to Kotlin.
- Remove both classes and create our own from scratch.
While the first options is great and works mostly flawless to auto-convert Java to Kotlin, let’s create a function from scratch. So drop the two classes, and then let’s add a new class HelloKotlin
:
package be.maartenballiauw.azure.ktfunction
class HelloKotlin {
}
Very clean code! Unfortunately, it does nothing yet. Let’s code a function that takes a String
input and returns another String
. Some sort of “Hello World”:
class HelloKotlin {
fun hello(name: String): String {
return "Hello $name"
}
}
This function is public, takes a non-nullable string (Kotlin dislikes null
, if you do want to support null
you could make it a String?
).
One more thing to do: adding an import
statement (the Java and Kotlin equivalent of .NET using
), and some annotations (the Java and Kotlin equivalent of .NET attributes) that will help us generate the required Azure Functions metadata to make our project run. This is not required, but makes life easier.
- Adding
@FunctionName("hello")
on the function level will tell the Azure Functions Maven plugin this method will be exposed as a function. - Adding
@HttpTrigger(name = "name", methods = arrayOf("get"), authLevel = AuthorizationLevel.ANONYMOUS)
on thename
parameter will help the Azure Functions Maven plugin to determine this parameter is really called “name” and can be available on anonymousGET
requests.
The full code:
package be.maartenballiauw.azure.ktfunction
import com.microsoft.azure.serverless.functions.annotation.*
class HelloKotlin {
@FunctionName("hello")
fun hello(
@HttpTrigger(name = "name", methods = arrayOf("get"), authLevel = AuthorizationLevel.ANONYMOUS)
name: String): String {
return "Hello $name"
}
}
We can now package our application (double-clicking the package
Maven goal), then run it (double-clicking the azure-functions \| azure-functions:run
Maven goal). Here’s where to find them, if you want you can run the package
goal right now but don’t run it just yet:
4. Including runtime dependencies in our Azure Functions artifact
If we’d run the package
target, we will find a ktfunction-1.0-SNAPSHOT.jar
file of roughly 5 KB in our project folder on disk (under the target
folder if you are curious). This contains our compiled Kotlin Azure Function, but it will not run. Not in the emulator. Not in the cloud.
We are missing one thing: our dependencies. When packaging our application, all we get is a package containing our own code, because the project system assumes dependencies can be found at runtime. That’s not true, unfortunately. The Azure Functions cloud environment will not know about Kotlin. If you’re using a database access library or a JSON serializer, the Azure Functions cloud environment will probably not know about it either. In short: we have to include all of our runtime dependencies in our package.
To add runtime dependencies to our deployment package, we can edit the pom.xml
file. This is the project model Maven uses to do its thing. For .NET developers, it’s a bit of a .csproj
mixed with a bit of NuGet and some additional MSBuild.
Under the <plugins>
element, we can add the following two plugins. Note these should be the first elements under the <plugins>
element, at least before the Azure Functions plugins that are in our pom.xml
.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest />
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
What does this do? Glad to hear you care!
- The
maven-jar-plugin
plugin builds a package based on what themaven-assembly-plugin
provides us. - The
maven-assembly-plugin
plugin builds the necessary files to be able to create a self-contained package containing all of our runtime dependencies. Note theappendAssemblyId
element has to be set tofalse
, so that the resulting JAR file name is correct for the Azure Functions tools to pick up!
Ready? We can now double-click the package
Maven goal to generate our deployment artifacts. If you see any failures, execute the clean
Maven goal first - it could be there are some leftover files lingering from before we enabled Kotlin in our project.
Once finished, we can see the resulting files pop up in our project folder:
We can see a .jar
file which contains our function code and dependencies. The size is now close to 1 MB (because of the dependencies being embedded). The function.json
file(s) have been generated based on the annotations we added earlier in code.
Before running, make sure to edit the local.settings.json
and add the required Azure Storage settings.
5. Running our Kotlin Azure Function
We’re there. All set. Ready to roll! We can double-click the azure-functions \| azure-functions:run
Maven goal and run our function in the emulator.
Note: I have been struggling with an Object reference not set to an instance of an object error at startup. Some back-an-forth e-mails with the Azure team learned this error typically means the JAVA_HOME
environment variable is not set. If you encounter this error, make sure to set it! Check the prerequisites again!
After startup, we can now invoke our Kotlin Azure Function. Either from the browser, hitting http://localhost:7071/api/hello?name=Maarten, or using IntelliJ IDEA’s built-in REST client (Tools | Test RESTFul Webservice):
The result value will be Hello, Maarten!
or whatever we wrote in our Kotlin Azure Function.
6. Debugging our Kotlin Azure Function
Running our Kotlin Azure Function is one thing, we may also want to debug it. This is a little bit tricky, because of how the Azure Functions runtime works.
In short, when we run our Kotlin Azure Function, we are actually running a Maven target, which runs dotnet.exe
, which in turn runs a Java worker process in which our function lives. That means three processes are involved:
- JVM running
azure-functions:run
goal dotnet.exe
running Azure Functions runtime- JVM running our Kotlin Azure Function
Our IDE will start #1, while we really want to debug #3. Good thing for us is that the JVM process running our Kotlin Azure Function also exposes itself for debuggers to attach to it!
This means that in IntelliJ IDEA, we can use the Run | Attach to Local Process… menu, and pick our worker process:
If we now place a breakpoint in our function and invoke our function, the debugger will pause our function and show us what is going on, allowing us to inspect variables, step through code, …
Conclusion
Running Kotlin in Azure Functions is quite easy. There’s a bit of setup work to make the packaging work properly (but in reality, that happens a lot with any Maven-based project), but apart from that we can run Kotlin on Azure Functions - both the emulator as well as production.
Looking for the sample project? Here you go - ktfunction.zip contains the full project I’ve used throughout this post.
Let me know if you try this, would love to see some Kotlin Azure Functions in production!
One response