In this lecture note, we will deploy a sample Java function for IP geocoding (determining the geographic location of an IP address) to demonstrate the use of Java in Azure Functions. The function will be HTTP-triggered, tested locally, and then deployed to serverless Azure Functions.
Geo IPv4 Function
We’ll create a serverless function that accepts an IPv4 address as input and returns the country code. The function will use an open dataset that provides geolocation data for IPv4 addresses worldwide.
Please note that the accuracy of this dataset is limited, and it should not be used in a production environment. For more reliable results, consider using the MaxMind GeoIP2 database, which requires an access key.
Prerequisites
- Install the Azure CLI
- Windows: Download and run the Azure CLI from here
- macOS: Use Homebrew to install the Core Tools on macOS using
brew update && brew install azure-cli
- Linux: use your package manager (e.g., APT) to install it as described here
- Install Apache Maven
- Windows
- Visit the Maven download page and download the binary zip archive of the latest version of Maven (e.g.,
apache-maven-<version>-bin.zip
). - Extract/unzip it to a new directory (e.g.,
C:\Program Files\Maven\
). - Open the Start menu and search for environment variables.
- click on Edit the system environment variables.
- Under the Advanced tab in the System Properties window, click Environment Variables.
- Click the New… button under the System variables section to add a new system environment variable.
- Enter MAVEN_HOME as the variable name and the path to the Maven installation directory as the variable value. Click OK.
- Under the System variables section, click on Path to select it and click the Edit… button.
- Click the New button in the Edit environment variable window.
- Enter
%MAVEN_HOME%\bin
in the new field. Click OK to save changes to the Path variable. Click OK - Close and open PowerShell, and run
mvn -version
to confirm that you have maven installed successfully.
- Visit the Maven download page and download the binary zip archive of the latest version of Maven (e.g.,
- macOS
- Install Homebrew if you do not already have it.
brew install maven
- Linux
- Use your package manager (e.g.,
apt install maven
)
- Use your package manager (e.g.,
- Windows
- The Java Development Kit (JDK), Java SE 8 or above.
- Sign in into Azure CLI
- Close and reopen any active terminal window and run:
az login
- When you sign in, Azure CLI generates and stores an authentication refresh token, which is used to obtain a new access token when the current one expires:
az account get-access-token
- Close and reopen any active terminal window and run:
Deploying an Azure Function in Java using Visual Studio Code
We will create and deploy the Java function code to Azure using Visual Studio Code.
Open Visual Studio Code, from the View menu, click Extensions or use the keyboard shortcut
Ctrl+Shift+X
(Windows/Linux) orShift+Command+X
(macOS), and install the following extensions published by Microsoft:Sign in to Azure in VS Code
- In Visual Studio Code, in the Activity bar, select the Azure icon. Then under Resources, select Sign in to Azure.
- In Visual Studio Code, in the Activity bar, select the Azure icon. Then under Resources, select Sign in to Azure.
Create an Azure Function App at Azure Portal
- In the Azure Portal, search for Function App and click on it
- Click + Create
- From the hosting options, select Flex Consumption plan and click Select.
- Under Basics:
- Subscription: Select your subscription and Resource Group
- Function App name: Choose a globally unique name (e.g.,
GeoByIPFunc-YOURNAME
) - Region: Select any region (e.g.,
Qatar Central
) - Runtime stack:
Java
- Version:
21
- Instance Size: Select
512 MB
or similar for the amount of memory needs to be allocated to each instance of the function app. - Zone redundancy: select
Disabled
- Under Networking: Make sure that Enable public access is turned On, and virtual network integration is Off.
- Under Monitoring: you may disable Application Insights.
- Under Resource authentication make sure that the selected Authentication type is Secrets
- Click Review + create then Create
- Once your deployment is complete, click on Go to resource.
Create the Function in VS Code
- Open a new Visual Studio Code window, press
F1
on Windows orCommand+SHIFT+P
on macOS and search for and run the commandAzure Functions: Create New Project....
Prompt Selection Select the directory location for storing your project workspace Create a new empty directory (e.g., GeoByIPFunc
) and choose it as the desired project directorySelect a language Java
Select a version of Java Java 21
Enter a Maven group id sa.edu.kau.fcit.cpit490
Enter a Maven artifact id GeoByIPFunc
Enter a Maven release version 1.0-SNAPSHOT
Enter a Java package name sa.edu.kau.fcit.cpit490
Enter the globally unique name for the App Name GeoByIPFunc-YOURNAME
Select build type Maven
How would you like to open your project? Open in current window - Open a new Visual Studio Code window, press
Write the Function Code
- We will use a CSV file that contains a list of IPv4 address ranges and the country code. This CSV file is part of this GitHub repository sapics/ip-location-db project.
- Open a Terminal window on VS Code (Terminal menu -> New Terminal) and download and move the
asn-country-ipv4-num.csv
file that contains the IPv4 address range and country codes:
curl -LO https://github.com/sapics/ip-location-db/raw/refs/heads/main/asn-country/asn-country-ipv4-num.csv
mkdir -p src/main/resources
mv asn-country-ipv4-num.csv src/main/resources/
- We will use a CSV parsing library as a dependency for our Maven project. Open the
pom.xml
file in VS code and add the following to yourpom.xml
file under the<dependencies>
tag:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.14.1</version>
</dependency>
- With the
pom.xml
file open, expand the left sidebar, select Maven, and click on the dual arrow icon to refresh the dependencies as shown below:
- Open the class
Function.java
, which is typically atsrc/main/java/sa/edu/kau/fcit/cpit490/Function.java
and replace the code with:
package sa.edu.kau.fcit.cpit490;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import java.util.Optional;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
/**
* Azure Functions with HTTP Trigger.
*/
public class Function {
/**
* This function listens at endpoint "/api/getLocation". To invoke it using
* "curl" command in bash:
* curl "{your host}/api/getLocation?ip=IPv4_ADDRESS"
*/
@FunctionName("getLocation")
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {
HttpMethod.GET }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");
// Get the IPv4 address as a query parameter
final String ipAddress = request.getQueryParameters().get("ip");
if (ipAddress == null) {
context.getLogger().warning("Java HTTP trigger processed a request.");
return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
.body("Please provide an IP address using the 'ip' query parameter")
.build();
} else {
try {
String countryCode = getCountryFromIP(ipAddress, context);
if (countryCode != null) {
Map<String, String> responseData = new HashMap<>();
responseData.put("ip", ipAddress);
responseData.put("countryCode", countryCode);
return request.createResponseBuilder(HttpStatus.OK).body(responseData).build();
} else {
return request.createResponseBuilder(HttpStatus.NOT_FOUND)
.body("Country not found for IP: " + ipAddress)
.build();
}
} catch (Exception e) {
context.getLogger().severe("Failed to find country for IP: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to find country for IP: " + e.getMessage())
.build();
}
}
}
/**
* Gets country code for an IPv4 address
*
* @param ipAddress The IPv4 address to look up
* @return The two-letter country code or null if not found
*/
public String getCountryFromIP(String ipAddress, ExecutionContext context) {
try {
long ipLong = ipToLong(ipAddress);
return lookupCountryCode(ipLong, context);
} catch (Exception e) {
context.getLogger().warning("Error looking up IP: " + e.getMessage());
return null;
}
}
/**
* Converts an IPv4 address to a long integer for range comparison
*
* @param ipAddress The IPv4 address in string format (e.g., "192.168.1.1")
* @return The IP address as a long integer
* @throws UnknownHostException if the IP address is invalid
*/
public long ipToLong(String ipAddress) throws UnknownHostException {
String[] parts = ipAddress.split("\\.");
if (parts.length != 4) {
throw new UnknownHostException("Invalid IPv4 format: " + ipAddress);
}
InetAddress inet = InetAddress.getByName(ipAddress);
byte[] addressBytes = inet.getAddress();
if (addressBytes.length != 4) {
throw new UnknownHostException("Not an IPv4 address");
}
long result = 0;
for (byte b : addressBytes) {
result = (result << 8) | (b & 0xFF);
}
return result;
}
/**
* Looks up the country code for an IP address using the CSV dataset
* Note: The dataset file "asn-country-ipv4-num.csv" should be placed in the
* resources folder src/main/resources/
* and have the format: start_ip,end_ip,country_code
*
* @param ipLong The IP address converted to a long integer
* @param context The Azure Functions execution context for logging
* @return The two-letter country code or null if not found
*/
public String lookupCountryCode(long ipLong, ExecutionContext context) {
try {
InputStream is = getResourceAsStream("asn-country-ipv4-num.csv");
if (is == null) {
context.getLogger().severe("Error reading CSV file: resource not found");
return null;
}
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8);
CSVParser csvParser = CSVFormat.DEFAULT.builder().get().parse(reader)) {
for (CSVRecord record : csvParser) {
if (record.size() < 3)
continue;
long startRange = Long.parseLong(record.get(0));
long endRange = Long.parseLong(record.get(1));
if (ipLong >= startRange && ipLong <= endRange) {
return record.get(2);
}
}
return null;
}
} catch (IOException e) {
context.getLogger().severe("Error reading CSV file: " + e.getMessage());
return null;
}
}
/**
* Gets resource as stream - method made accessible for testing
*
* @param resourceName The name of the resource
* @return InputStream of the resource or null if not found
*/
public InputStream getResourceAsStream(String resourceName) {
return getClass().getClassLoader().getResourceAsStream(resourceName);
}
}
- Add the following two test files FunctionTest.java and HttpResponseMessageMock.java under
src/test/java/sa/edu/kau/fcit/cpit490/
- Start the function Locally
- You can start the function locally using one of the following options:
- Option 1: Using the VS Code Terminal. TErminal -> New Terminal and run
mvn clean package mvn azure-functions:run
- Option 2: Using the VS Code Maven and Azure Functions extensions
- From the left-hand side Activity bar, open the project explorer, select Maven -> Lifecycle -> Package as shown below:
- Open the Command Palette (
Ctrl+Shift+X
(Windows/Linux) orShift+Command+X
(macOS)) and type:Tasks: Run Task
->func: host start
- From the left-hand side Activity bar, open the project explorer, select Maven -> Lifecycle -> Package as shown below:
- The function should be running at
http://localhost:7071/api/getLocation
. Copy this URL.Note: Please note that any changes made to the source code will require re-packaging the Maven project and re-running the function.
- Option 1: Using the VS Code Terminal. TErminal -> New Terminal and run
- Test the function locally
- Find your public IP address by either going to a website such as https://whatismyipaddress.com/ or running
curl https://ipinfo.io/ip
- Test the function using:
curl http://localhost:7071/api/getLocation?ip=192.162.72.233
- Find your public IP address by either going to a website such as https://whatismyipaddress.com/ or running
{
"countryCode": "SA",
"ip": "192.162.72.233"
}
- You may also test the function using Postman:
- Publish the Function to Azure We will deploy the function project files directly to our Azure function app using zip deployment.
- Stop the function that you ran locally using
CTRL-C
. - Deploy the project files from the current directory to the
<FunctionAppName>
as a .zip deployment package. Replace<FunctionAppName>
with the name you chose on Azure in step 3 (e.g.,GeoByIPFunc-YOURNAME
). - From the VS Code Terminal tab, run:
func azure functionapp publish <FunctionAppName>
- Copy the domain of the function app from the Azure Portal.
- Test the function from the same Terminal Window you used to publish it using
https://<azure_function_app_domain>/<function_path>?ip=<ip_address>
:
curl "https://geobyipfunc-cpit490.azurewebsites.net/api/getLocation?ip=192.162.72.233"
- Testing the function With Auth Keys
- Testing the function from any location where the Azure session token has expired requires authentication keys. Azure Function requires authentication by default. Azure Functions are created with
AuthLevel.FUNCTION
, which means you need to include a function key in your request.- Get the function key using this command after replacing the function app name and resource group name with yours:
az functionapp keys list --name sentiment-function-cpit490 --resource-group cpit490
{
"functionKeys": {
"default": "JEpnsadfsdfsdfwlbsWjnp8i5cSWYhF9CDUAXaxk97isI8CAzFupfw_lQ==a"
},
"masterKey": "abcdefghijklmnopqrswvn7hb4Y4jT65zXJ99DAzFuun8tWA==",
"systemKeys": {}
}
- Copy the auth key (functionKeys.default) and include it as a query parameter as follows:
curl -X POST "<AZURE_FUNCTION_DEFAULT_DOMAIN>?code=<FUNCTION_AUTH_KEY>" \
-H "Content-Type: application/json" \
-d '{"text": "I love this product! It is amazing!"}'
- For example:
curl -X POST "https://sentiment-function-cpit490-hqebaxexbcead9bs.canadacentral-01.azurewebsites.net/api/http_trigger_sentiment_analysis?code=JEpnsadfsdfsdfwlbsWjnp8i5cSWYhF9CDUAXaxk97isI8CAzFupfw_lQ==a" \
-H "Content-Type: application/json" \
-d '{"text": "I love this product! It is amazing!"}'
- Testing with Postman
- Open Postman and create a new POST request
- Set the URL to your Azure Function endpoint with the auth key as a query parameter:
https://sentiment-function-cpit490-hqebaxexbcead9bs.canadacentral-01.azurewebsites.net/api/http_trigger_sentiment_analysis
- Under Query Params, add
- Key:
code
- Value: The auth key (functionKeys.default) you obtained from running
az functionapp keys list --name sentiment-function-cpit490 --resource-group cpit490
:
- Key:
- Under Headers tab, add:
- Key:
Content-Type
- Value:
application/json
- Key:
- Under Body tab:
- Select raw
- Choose JSON from the dropdown menu
- Enter the following JSON:
{"text": "I love this product! It is amazing!"}
- Click Send to test the function.
- You should receive a JSON response similar to:
{"sentiment": "positive", "polarity": 0.6875, "subjectivity": 0.75}
- Example Postman configuration: