Azure Functions Java Worker: Complete Implementation Guide

Azure Functions Java Worker enables running Java functions in Azure Functions runtime. This guide covers creating, deploying, and managing Java functions with Azure Functions.


Setup and Dependencies

1. Maven Configuration
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.company.azure.functions</groupId>
<artifactId>azure-java-functions</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<functionAppName>java-functions-app</functionAppName>
<functionAppRegion>eastus</functionAppRegion>
<stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
<azure.functions.maven.plugin.version>1.26.0</azure.functions.maven.plugin.version>
<azure.functions.java.library.version>3.0.0</azure.functions.java.library.version>
</properties>
<dependencies>
<!-- Azure Functions Java Library -->
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library</artifactId>
<version>${azure.functions.java.library.version}</version>
</dependency>
<!-- Azure Storage SDK for Blob, Queue, Table bindings -->
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.20.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-queue</artifactId>
<version>12.16.0</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library-test</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>${azure.functions.maven.plugin.version}</version>
<configuration>
<appName>${functionAppName}</appName>
<resourceGroup>java-functions-rg</resourceGroup>
<appServicePlanName>java-functions-asp</appServicePlanName>
<region>${functionAppRegion}</region>
<runtime>
<os>windows</os>
<javaVersion>11</javaVersion>
</runtime>
<appSettings>
<property>
<name>FUNCTIONS_EXTENSION_VERSION</name>
<value>~4</value>
</property>
<property>
<name>FUNCTIONS_WORKER_RUNTIME</name>
<value>java</value>
</property>
</appSettings>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
</project>
2. Host Configuration
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[2.*, 3.0.0)"
},
"extensions": {
"http": {
"routePrefix": "api",
"maxOutstandingRequests": 200,
"maxConcurrentRequests": 100,
"dynamicThrottlesEnabled": true
},
"queues": {
"batchSize": 16,
"newBatchThreshold": 8,
"maxDequeueCount": 5,
"maxPollingInterval": "00:00:02"
},
"serviceBus": {
"prefetchCount": 100,
"messageHandlerOptions": {
"autoComplete": true,
"maxConcurrentCalls": 32,
"maxAutoRenewDuration": "00:05:00"
}
}
},
"logging": {
"fileLoggingMode": "debugOnly",
"logLevel": {
"default": "Information",
"Host.Results": "Error",
"Function": "Information",
"Host.Aggregator": "Trace"
},
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond": 5
}
}
},
"functionTimeout": "00:10:00",
"healthMonitor": {
"enabled": true,
"healthCheckInterval": "00:00:10",
"healthCheckWindow": "00:02:00",
"healthCheckThreshold": 6,
"counterThreshold": 0.80
}
}

Core Function Implementations

1. HTTP Trigger Functions
package com.company.azure.functions;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* HTTP Trigger Functions for User Management
*/
public class UserFunctions {
private static final Logger LOGGER = Logger.getLogger(UserFunctions.class.getName());
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Map<String, User> USER_STORE = new ConcurrentHashMap<>();
private static final Random RANDOM = new Random();
/**
* HTTP POST - Create User
*/
@FunctionName("createUser")
public HttpResponseMessage createUser(
@HttpTrigger(
name = "req",
methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.FUNCTION,
route = "users"
) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request to create user.");
try {
// Parse request body
String requestBody = request.getBody().orElseThrow(() -> 
new IllegalArgumentException("Request body is required"));
UserCreateRequest createRequest = OBJECT_MAPPER.readValue(requestBody, UserCreateRequest.class);
// Validate input
if (createRequest.getEmail() == null || createRequest.getEmail().trim().isEmpty()) {
return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
.body("{\"error\": \"Email is required\"}")
.header("Content-Type", "application/json")
.build();
}
// Check for duplicate email
boolean emailExists = USER_STORE.values().stream()
.anyMatch(user -> user.getEmail().equalsIgnoreCase(createRequest.getEmail()));
if (emailExists) {
return request.createResponseBuilder(HttpStatus.CONFLICT)
.body("{\"error\": \"User with this email already exists\"}")
.header("Content-Type", "application/json")
.build();
}
// Create user
String userId = "user-" + System.currentTimeMillis() + "-" + RANDOM.nextInt(1000);
User user = new User(
userId,
createRequest.getEmail(),
createRequest.getFirstName(),
createRequest.getLastName(),
System.currentTimeMillis()
);
USER_STORE.put(userId, user);
// Log success
context.getLogger().info("User created successfully: " + userId);
// Return response
UserResponse response = new UserResponse(
"User created successfully",
user
);
return request.createResponseBuilder(HttpStatus.CREATED)
.body(OBJECT_MAPPER.writeValueAsString(response))
.header("Content-Type", "application/json")
.build();
} catch (IOException e) {
context.getLogger().severe("Error parsing request body: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
.body("{\"error\": \"Invalid JSON format\"}")
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error creating user: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Internal server error\"}")
.header("Content-Type", "application/json")
.build();
}
}
/**
* HTTP GET - Get User by ID
*/
@FunctionName("getUser")
public HttpResponseMessage getUser(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.FUNCTION,
route = "users/{id}"
) HttpRequestMessage<Optional<String>> request,
@BindingName("id") String userId,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request to get user: " + userId);
try {
User user = USER_STORE.get(userId);
if (user == null) {
return request.createResponseBuilder(HttpStatus.NOT_FOUND)
.body("{\"error\": \"User not found\"}")
.header("Content-Type", "application/json")
.build();
}
UserResponse response = new UserResponse(
"User retrieved successfully",
user
);
return request.createResponseBuilder(HttpStatus.OK)
.body(OBJECT_MAPPER.writeValueAsString(response))
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error retrieving user: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Internal server error\"}")
.header("Content-Type", "application/json")
.build();
}
}
/**
* HTTP GET - List All Users
*/
@FunctionName("listUsers")
public HttpResponseMessage listUsers(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.FUNCTION,
route = "users"
) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request to list users.");
try {
List<User> users = new ArrayList<>(USER_STORE.values());
UserListResponse response = new UserListResponse(
"Users retrieved successfully",
users.size(),
users
);
return request.createResponseBuilder(HttpStatus.OK)
.body(OBJECT_MAPPER.writeValueAsString(response))
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error listing users: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Internal server error\"}")
.header("Content-Type", "application/json")
.build();
}
}
/**
* HTTP PUT - Update User
*/
@FunctionName("updateUser")
public HttpResponseMessage updateUser(
@HttpTrigger(
name = "req",
methods = {HttpMethod.PUT},
authLevel = AuthorizationLevel.FUNCTION,
route = "users/{id}"
) HttpRequestMessage<Optional<String>> request,
@BindingName("id") String userId,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request to update user: " + userId);
try {
User existingUser = USER_STORE.get(userId);
if (existingUser == null) {
return request.createResponseBuilder(HttpStatus.NOT_FOUND)
.body("{\"error\": \"User not found\"}")
.header("Content-Type", "application/json")
.build();
}
String requestBody = request.getBody().orElseThrow(() -> 
new IllegalArgumentException("Request body is required"));
UserUpdateRequest updateRequest = OBJECT_MAPPER.readValue(requestBody, UserUpdateRequest.class);
// Update user
User updatedUser = new User(
existingUser.getId(),
existingUser.getEmail(), // Email cannot be changed
updateRequest.getFirstName() != null ? updateRequest.getFirstName() : existingUser.getFirstName(),
updateRequest.getLastName() != null ? updateRequest.getLastName() : existingUser.getLastName(),
existingUser.getCreatedAt()
);
USER_STORE.put(userId, updatedUser);
UserResponse response = new UserResponse(
"User updated successfully",
updatedUser
);
return request.createResponseBuilder(HttpStatus.OK)
.body(OBJECT_MAPPER.writeValueAsString(response))
.header("Content-Type", "application/json")
.build();
} catch (IOException e) {
context.getLogger().severe("Error parsing request body: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
.body("{\"error\": \"Invalid JSON format\"}")
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error updating user: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Internal server error\"}")
.header("Content-Type", "application/json")
.build();
}
}
/**
* HTTP DELETE - Delete User
*/
@FunctionName("deleteUser")
public HttpResponseMessage deleteUser(
@HttpTrigger(
name = "req",
methods = {HttpMethod.DELETE},
authLevel = AuthorizationLevel.FUNCTION,
route = "users/{id}"
) HttpRequestMessage<Optional<String>> request,
@BindingName("id") String userId,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request to delete user: " + userId);
try {
User removedUser = USER_STORE.remove(userId);
if (removedUser == null) {
return request.createResponseBuilder(HttpStatus.NOT_FOUND)
.body("{\"error\": \"User not found\"}")
.header("Content-Type", "application/json")
.build();
}
DeleteResponse response = new DeleteResponse(
"User deleted successfully",
userId,
System.currentTimeMillis()
);
return request.createResponseBuilder(HttpStatus.OK)
.body(OBJECT_MAPPER.writeValueAsString(response))
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error deleting user: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Internal server error\"}")
.header("Content-Type", "application/json")
.build();
}
}
// Data Models
public static class UserCreateRequest {
private String email;
private String firstName;
private String lastName;
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
public static class UserUpdateRequest {
private String firstName;
private String lastName;
// Getters and setters
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
public static class User {
private final String id;
private final String email;
private final String firstName;
private final String lastName;
private final long createdAt;
public User(String id, String email, String firstName, String lastName, long createdAt) {
this.id = id;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
this.createdAt = createdAt;
}
// Getters
public String getId() { return id; }
public String getEmail() { return email; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public long getCreatedAt() { return createdAt; }
}
public static class UserResponse {
private final String message;
private final User user;
public UserResponse(String message, User user) {
this.message = message;
this.user = user;
}
// Getters
public String getMessage() { return message; }
public User getUser() { return user; }
}
public static class UserListResponse {
private final String message;
private final int total;
private final List<User> users;
public UserListResponse(String message, int total, List<User> users) {
this.message = message;
this.total = total;
this.users = users;
}
// Getters
public String getMessage() { return message; }
public int getTotal() { return total; }
public List<User> getUsers() { return users; }
}
public static class DeleteResponse {
private final String message;
private final String userId;
private final long deletedAt;
public DeleteResponse(String message, String userId, long deletedAt) {
this.message = message;
this.userId = userId;
this.deletedAt = deletedAt;
}
// Getters
public String getMessage() { return message; }
public String getUserId() { return userId; }
public long getDeletedAt() { return deletedAt; }
}
}
2. Blob Storage Trigger Functions
package com.company.azure.functions;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
/**
* Blob Storage Trigger Functions
*/
public class BlobStorageFunctions {
private static final Logger LOGGER = Logger.getLogger(BlobStorageFunctions.class.getName());
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static BlobServiceClient blobServiceClient;
static {
// Initialize Blob Service Client
String connectionString = System.getenv("AzureWebJobsStorage");
blobServiceClient = new BlobServiceClientBuilder()
.connectionString(connectionString)
.buildClient();
}
/**
* Blob Trigger - Process new user data files
*/
@FunctionName("processUserDataBlob")
public void processUserDataBlob(
@BlobTrigger(
name = "content",
path = "user-data/{name}",
dataType = "binary",
connection = "AzureWebJobsStorage"
) byte[] content,
@BindingName("name") String filename,
final ExecutionContext context) {
context.getLogger().info("Java Blob trigger function processed a blob: " + filename);
try {
String fileContent = new String(content, StandardCharsets.UTF_8);
context.getLogger().info("Blob content: " + fileContent);
// Process the blob content
UserBatchData batchData = OBJECT_MAPPER.readValue(fileContent, UserBatchData.class);
context.getLogger().info(String.format(
"Processing batch data with %d users", batchData.getUsers().size()));
// Process each user in the batch
for (UserBatchData.UserData userData : batchData.getUsers()) {
processUserData(userData, context);
}
// Move processed blob to archive container
moveBlobToArchive(filename, content, context);
context.getLogger().info("Successfully processed blob: " + filename);
} catch (Exception e) {
context.getLogger().severe("Error processing blob " + filename + ": " + e.getMessage());
// Move failed blob to error container
moveBlobToError(filename, content, context);
}
}
/**
* Blob Output - Generate user report
*/
@FunctionName("generateUserReport")
public HttpResponseMessage generateUserReport(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.FUNCTION,
route = "reports/users"
) HttpRequestMessage<Optional<String>> request,
@BlobOutput(
name = "report",
path = "reports/user-report-{datetime}.json",
connection = "AzureWebJobsStorage"
) OutputBinding<String> reportOutput,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger function processed a request to generate user report.");
try {
// Generate report data
UserReport report = generateUserReportData();
String reportJson = OBJECT_MAPPER.writeValueAsString(report);
// Set output blob content
reportOutput.setValue(reportJson);
return request.createResponseBuilder(HttpStatus.OK)
.body("{\"message\": \"User report generated successfully\", \"reportPath\": \"reports/user-report-\" + System.currentTimeMillis() + \".json\"}")
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error generating user report: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Failed to generate report\"}")
.header("Content-Type", "application/json")
.build();
}
}
private void processUserData(UserBatchData.UserData userData, ExecutionContext context) {
context.getLogger().info("Processing user: " + userData.getEmail());
// Implement user processing logic
// This could include validation, transformation, storage, etc.
try {
Thread.sleep(100); // Simulate processing time
context.getLogger().info("Successfully processed user: " + userData.getEmail());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
context.getLogger().warning("User processing interrupted: " + userData.getEmail());
}
}
private void moveBlobToArchive(String filename, byte[] content, ExecutionContext context) {
try {
BlobContainerClient archiveContainer = blobServiceClient.getBlobContainerClient("user-data-archive");
if (!archiveContainer.exists()) {
archiveContainer.create();
}
BlobClient archiveBlob = archiveContainer.getBlobClient(filename);
archiveBlob.upload(new ByteArrayInputStream(content), content.length, true);
// Delete from original container
BlobContainerClient sourceContainer = blobServiceClient.getBlobContainerClient("user-data");
BlobClient sourceBlob = sourceContainer.getBlobClient(filename);
sourceBlob.delete();
context.getLogger().info("Moved blob to archive: " + filename);
} catch (Exception e) {
context.getLogger().severe("Error moving blob to archive: " + e.getMessage());
}
}
private void moveBlobToError(String filename, byte[] content, ExecutionContext context) {
try {
BlobContainerClient errorContainer = blobServiceClient.getBlobContainerClient("user-data-errors");
if (!errorContainer.exists()) {
errorContainer.create();
}
BlobClient errorBlob = errorContainer.getBlobClient(filename);
errorBlob.upload(new ByteArrayInputStream(content), content.length, true);
// Delete from original container
BlobContainerClient sourceContainer = blobServiceClient.getBlobContainerClient("user-data");
BlobClient sourceBlob = sourceContainer.getBlobClient(filename);
sourceBlob.delete();
context.getLogger().info("Moved blob to error container: " + filename);
} catch (Exception e) {
context.getLogger().severe("Error moving blob to error container: " + e.getMessage());
}
}
private UserReport generateUserReportData() {
UserReport report = new UserReport();
report.setGeneratedAt(System.currentTimeMillis());
report.setTotalUsers(UserFunctions.USER_STORE.size());
report.setActiveUsers(UserFunctions.USER_STORE.size()); // Simplified
UserReport.ReportSummary summary = new UserReport.ReportSummary();
summary.setUserCount(UserFunctions.USER_STORE.size());
summary.setGenerationTime(System.currentTimeMillis());
report.setSummary(summary);
return report;
}
// Data Models for Blob Processing
public static class UserBatchData {
private String batchId;
private long processedAt;
private List<UserData> users;
// Getters and setters
public String getBatchId() { return batchId; }
public void setBatchId(String batchId) { this.batchId = batchId; }
public long getProcessedAt() { return processedAt; }
public void setProcessedAt(long processedAt) { this.processedAt = processedAt; }
public List<UserData> getUsers() { return users; }
public void setUsers(List<UserData> users) { this.users = users; }
public static class UserData {
private String email;
private String firstName;
private String lastName;
private String department;
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
}
}
public static class UserReport {
private long generatedAt;
private int totalUsers;
private int activeUsers;
private ReportSummary summary;
// Getters and setters
public long getGeneratedAt() { return generatedAt; }
public void setGeneratedAt(long generatedAt) { this.generatedAt = generatedAt; }
public int getTotalUsers() { return totalUsers; }
public void setTotalUsers(int totalUsers) { this.totalUsers = totalUsers; }
public int getActiveUsers() { return activeUsers; }
public void setActiveUsers(int activeUsers) { this.activeUsers = activeUsers; }
public ReportSummary getSummary() { return summary; }
public void setSummary(ReportSummary summary) { this.summary = summary; }
public static class ReportSummary {
private int userCount;
private long generationTime;
// Getters and setters
public int getUserCount() { return userCount; }
public void setUserCount(int userCount) { this.userCount = userCount; }
public long getGenerationTime() { return generationTime; }
public void setGenerationTime(long generationTime) { this.generationTime = generationTime; }
}
}
}
3. Queue Trigger Functions
package com.company.azure.functions;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
import java.util.logging.Logger;
/**
* Queue Storage Trigger Functions
*/
public class QueueFunctions {
private static final Logger LOGGER = Logger.getLogger(QueueFunctions.class.getName());
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* Queue Trigger - Process user registration messages
*/
@FunctionName("processUserRegistrationQueue")
public void processUserRegistrationQueue(
@QueueTrigger(
name = "message",
queueName = "user-registrations",
connection = "AzureWebJobsStorage"
) String message,
@QueueOutput(
name = "output",
queueName = "user-registrations-processed",
connection = "AzureWebJobsStorage"
) OutputBinding<String> output,
@CosmosDBOutput(
name = "databaseOutput",
databaseName = "UserDatabase",
collectionName = "UserCollection",
connectionStringSetting = "CosmosDBConnection"
) OutputBinding<String> cosmosOutput,
final ExecutionContext context) {
context.getLogger().info("Java Queue trigger function processed a message: " + message);
try {
// Parse queue message
UserRegistrationMessage registrationMessage = 
OBJECT_MAPPER.readValue(message, UserRegistrationMessage.class);
context.getLogger().info("Processing user registration: " + registrationMessage.getEmail());
// Process registration
processRegistration(registrationMessage, context);
// Create processed message
ProcessedRegistrationMessage processedMessage = new ProcessedRegistrationMessage(
registrationMessage.getEmail(),
registrationMessage.getUserId(),
"SUCCESS",
System.currentTimeMillis()
);
String processedMessageJson = OBJECT_MAPPER.writeValueAsString(processedMessage);
output.setValue(processedMessageJson);
// Store in CosmosDB
cosmosOutput.setValue(OBJECT_MAPPER.writeValueAsString(registrationMessage));
context.getLogger().info("Successfully processed user registration: " + registrationMessage.getEmail());
} catch (Exception e) {
context.getLogger().severe("Error processing queue message: " + e.getMessage());
// Send to poison queue
handleFailedMessage(message, e.getMessage(), context);
}
}
/**
* Queue Trigger - Process user notification messages
*/
@FunctionName("processUserNotificationQueue")
@StorageAccount("AzureWebJobsStorage")
public void processUserNotificationQueue(
@QueueTrigger(
name = "message",
queueName = "user-notifications"
) String message,
@SendGridOutput(
name = "emailOutput",
apiKey = "SendGridApiKey",
to = "{email}",
from = "[email protected]",
subject = "Notification from User Service",
text = "{message}"
) OutputBinding<SendGridMessage> emailOutput,
final ExecutionContext context) {
context.getLogger().info("Java Queue trigger function processed a notification: " + message);
try {
UserNotificationMessage notification = 
OBJECT_MAPPER.readValue(message, UserNotificationMessage.class);
// Create email message
SendGridMessage emailMessage = new SendGridMessage();
emailMessage.setTo(notification.getEmail());
emailMessage.setSubject(notification.getSubject());
emailMessage.setContent(notification.getMessage());
emailOutput.setValue(emailMessage);
context.getLogger().info("Sent notification email to: " + notification.getEmail());
} catch (Exception e) {
context.getLogger().severe("Error processing notification: " + e.getMessage());
}
}
/**
* HTTP Trigger - Add message to queue
*/
@FunctionName("addUserToQueue")
public HttpResponseMessage addUserToQueue(
@HttpTrigger(
name = "req",
methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.FUNCTION,
route = "queue/users"
) HttpRequestMessage<Optional<String>> request,
@QueueOutput(
name = "output",
queueName = "user-registrations",
connection = "AzureWebJobsStorage"
) OutputBinding<String> output,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger function processed a request to add user to queue.");
try {
String requestBody = request.getBody().orElseThrow(() -> 
new IllegalArgumentException("Request body is required"));
UserRegistrationMessage registrationMessage = 
OBJECT_MAPPER.readValue(requestBody, UserRegistrationMessage.class);
// Generate user ID if not provided
if (registrationMessage.getUserId() == null) {
registrationMessage.setUserId("user-" + System.currentTimeMillis());
}
registrationMessage.setQueuedAt(System.currentTimeMillis());
String queueMessage = OBJECT_MAPPER.writeValueAsString(registrationMessage);
output.setValue(queueMessage);
return request.createResponseBuilder(HttpStatus.ACCEPTED)
.body("{\"message\": \"User added to queue for processing\", \"userId\": \"" + registrationMessage.getUserId() + "\"}")
.header("Content-Type", "application/json")
.build();
} catch (Exception e) {
context.getLogger().severe("Error adding user to queue: " + e.getMessage());
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Failed to add user to queue\"}")
.header("Content-Type", "application/json")
.build();
}
}
private void processRegistration(UserRegistrationMessage message, ExecutionContext context) {
context.getLogger().info("Processing registration for: " + message.getEmail());
// Simulate processing time
try {
Thread.sleep(500);
// Simulate random failures for testing
if (Math.random() < 0.1) { // 10% failure rate for testing
throw new RuntimeException("Simulated processing failure");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Processing interrupted", e);
}
}
private void handleFailedMessage(String originalMessage, String error, ExecutionContext context) {
try {
FailedMessage failedMessage = new FailedMessage(
originalMessage,
error,
System.currentTimeMillis()
);
String failedMessageJson = OBJECT_MAPPER.writeValueAsString(failedMessage);
// In a real scenario, you would send this to a poison queue
context.getLogger().severe("Message failed processing: " + failedMessageJson);
} catch (Exception e) {
context.getLogger().severe("Error handling failed message: " + e.getMessage());
}
}
// Data Models for Queue Processing
public static class UserRegistrationMessage {
private String userId;
private String email;
private String firstName;
private String lastName;
private long queuedAt;
// Getters and setters
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public long getQueuedAt() { return queuedAt; }
public void setQueuedAt(long queuedAt) { this.queuedAt = queuedAt; }
}
public static class ProcessedRegistrationMessage {
private final String email;
private final String userId;
private final String status;
private final long processedAt;
public ProcessedRegistrationMessage(String email, String userId, String status, long processedAt) {
this.email = email;
this.userId = userId;
this.status = status;
this.processedAt = processedAt;
}
// Getters
public String getEmail() { return email; }
public String getUserId() { return userId; }
public String getStatus() { return status; }
public long getProcessedAt() { return processedAt; }
}
public static class UserNotificationMessage {
private String email;
private String subject;
private String message;
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
public static class FailedMessage {
private final String originalMessage;
private final String error;
private final long failedAt;
public FailedMessage(String originalMessage, String error, long failedAt) {
this.originalMessage = originalMessage;
this.error = error;
this.failedAt = failedAt;
}
// Getters
public String getOriginalMessage() { return originalMessage; }
public String getError() { return error; }
public long getFailedAt() { return failedAt; }
}
public static class SendGridMessage {
private String to;
private String subject;
private String content;
// Getters and setters
public String getTo() { return to; }
public void setTo(String to) { this.to = to; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
}
4. Timer Trigger Functions
package com.company.azure.functions;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.util.*;
import java.util.logging.Logger;
/**
* Timer Trigger Functions for Scheduled Tasks
*/
public class TimerFunctions {
private static final Logger LOGGER = Logger.getLogger(TimerFunctions.class.getName());
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* Timer Trigger - Daily user cleanup
*/
@FunctionName("dailyUserCleanup")
public void dailyUserCleanup(
@TimerTrigger(
name = "timerInfo",
schedule = "0 0 2 * * *" // 2 AM daily
) String timerInfo,
@CosmosDBOutput(
name = "databaseOutput",
databaseName = "UserDatabase",
collectionName = "UserAudit",
connectionStringSetting = "CosmosDBConnection"
) OutputBinding<String> auditOutput,
final ExecutionContext context) {
context.getLogger().info("Java Timer trigger function executed at: " + Instant.now());
try {
CleanupResult result = performUserCleanup(context);
// Create audit record
AuditRecord auditRecord = new AuditRecord(
"dailyUserCleanup",
result.getDeletedCount(),
result.getProcessedCount(),
System.currentTimeMillis()
);
auditOutput.setValue(OBJECT_MAPPER.writeValueAsString(auditRecord));
context.getLogger().info(String.format(
"Daily cleanup completed. Deleted: %d, Processed: %d",
result.getDeletedCount(), result.getProcessedCount()));
} catch (Exception e) {
context.getLogger().severe("Error in daily user cleanup: " + e.getMessage());
}
}
/**
* Timer Trigger - Hourly user statistics
*/
@FunctionName("hourlyUserStats")
public void hourlyUserStats(
@TimerTrigger(
name = "timerInfo",
schedule = "0 0 * * * *" // Every hour
) String timerInfo,
@BlobOutput(
name = "statsOutput",
path = "stats/user-stats-{datetime}.json",
connection = "AzureWebJobsStorage"
) OutputBinding<String> statsOutput,
final ExecutionContext context) {
context.getLogger().info("Java Timer trigger function executed for hourly stats at: " + Instant.now());
try {
UserStats stats = collectUserStats();
String statsJson = OBJECT_MAPPER.writeValueAsString(stats);
statsOutput.setValue(statsJson);
context.getLogger().info("Hourly user stats generated: " + stats.getTotalUsers());
} catch (Exception e) {
context.getLogger().severe("Error generating hourly stats: " + e.getMessage());
}
}
/**
* Timer Trigger - Every 5 minutes health check
*/
@FunctionName("healthCheck")
public void healthCheck(
@TimerTrigger(
name = "timerInfo",
schedule = "0 */5 * * * *" // Every 5 minutes
) String timerInfo,
final ExecutionContext context) {
context.getLogger().info("Java Timer trigger function executed for health check at: " + Instant.now());
try {
HealthCheckResult result = performHealthCheck(context);
if (result.isHealthy()) {
context.getLogger().info("Health check passed: " + result.getMessage());
} else {
context.getLogger().warning("Health check failed: " + result.getMessage());
// Could send alert here
}
} catch (Exception e) {
context.getLogger().severe("Error performing health check: " + e.getMessage());
}
}
private CleanupResult performUserCleanup(ExecutionContext context) {
context.getLogger().info("Performing user cleanup...");
// Simulate cleanup process
int deletedCount = 0;
int processedCount = UserFunctions.USER_STORE.size();
// In a real scenario, you would:
// 1. Find inactive users
// 2. Archive their data
// 3. Remove from active storage
// 4. Update audit records
try {
Thread.sleep(2000); // Simulate cleanup time
deletedCount = (int) (Math.random() * 10); // Simulate random deletions
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new CleanupResult(deletedCount, processedCount);
}
private UserStats collectUserStats() {
UserStats stats = new UserStats();
stats.setTimestamp(System.currentTimeMillis());
stats.setTotalUsers(UserFunctions.USER_STORE.size());
stats.setActiveUsers(UserFunctions.USER_STORE.size()); // Simplified
// Collect additional statistics
Map<String, Integer> statsByHour = new HashMap<>();
for (int i = 0; i < 24; i++) {
statsByHour.put(String.valueOf(i), (int) (Math.random() * 100));
}
stats.setRegistrationsByHour(statsByHour);
return stats;
}
private HealthCheckResult performHealthCheck(ExecutionContext context) {
context.getLogger().info("Performing health check...");
// Check various system health indicators
boolean storageHealthy = checkStorageHealth();
boolean databaseHealthy = checkDatabaseHealth();
boolean memoryHealthy = checkMemoryHealth();
boolean overallHealthy = storageHealthy && databaseHealthy && memoryHealthy;
String message = String.format(
"Storage: %s, Database: %s, Memory: %s",
storageHealthy ? "OK" : "FAIL",
databaseHealthy ? "OK" : "FAIL",
memoryHealthy ? "OK" : "FAIL"
);
return new HealthCheckResult(overallHealthy, message);
}
private boolean checkStorageHealth() {
// Implement storage health check
return Math.random() > 0.1; // 90% success rate for simulation
}
private boolean checkDatabaseHealth() {
// Implement database health check
return Math.random() > 0.1; // 90% success rate for simulation
}
private boolean checkMemoryHealth() {
// Implement memory health check
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
double memoryUsage = (double) usedMemory / maxMemory;
return memoryUsage < 0.8; // Healthy if using less than 80% memory
}
// Data Models for Timer Functions
public static class CleanupResult {
private final int deletedCount;
private final int processedCount;
public CleanupResult(int deletedCount, int processedCount) {
this.deletedCount = deletedCount;
this.processedCount = processedCount;
}
// Getters
public int getDeletedCount() { return deletedCount; }
public int getProcessedCount() { return processedCount; }
}
public static class AuditRecord {
private final String operation;
private final int affectedRecords;
private final int totalRecords;
private final long timestamp;
public AuditRecord(String operation, int affectedRecords, int totalRecords, long timestamp) {
this.operation = operation;
this.affectedRecords = affectedRecords;
this.totalRecords = totalRecords;
this.timestamp = timestamp;
}
// Getters
public String getOperation() { return operation; }
public int getAffectedRecords() { return affectedRecords; }
public int getTotalRecords() { return totalRecords; }
public long getTimestamp() { return timestamp; }
}
public static class UserStats {
private long timestamp;
private int totalUsers;
private int activeUsers;
private Map<String, Integer> registrationsByHour;
// Getters and setters
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public int getTotalUsers() { return totalUsers; }
public void setTotalUsers(int totalUsers) { this.totalUsers = totalUsers; }
public int getActiveUsers() { return activeUsers; }
public void setActiveUsers(int activeUsers) { this.activeUsers = activeUsers; }
public Map<String, Integer> getRegistrationsByHour() { return registrationsByHour; }
public void setRegistrationsByHour(Map<String, Integer> registrationsByHour) { this.registrationsByHour = registrationsByHour; }
}
public static class HealthCheckResult {
private final boolean healthy;
private final String message;
public HealthCheckResult(boolean healthy, String message) {
this.healthy = healthy;
this.message = message;
}
// Getters
public boolean isHealthy() { return healthy; }
public String getMessage() { return message; }
}
}

Configuration and Deployment

1. Application Settings
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=yourstorageaccount;AccountKey=yourkey;EndpointSuffix=core.windows.net",
"FUNCTIONS_WORKER_RUNTIME": "java",
"FUNCTIONS_EXTENSION_VERSION": "~4",
"APPINSIGHTS_INSTRUMENTATIONKEY": "your-app-insights-key",
"CosmosDBConnection": "AccountEndpoint=https://your-cosmosdb.documents.azure.com:443/;AccountKey=yourkey;",
"SendGridApiKey": "your-sendgrid-api-key",
"ServiceBusConnection": "Endpoint=sb://yourservicebus.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=yourkey"
}
}
2. Deployment Script
#!/bin/bash
# deploy-azure-functions.sh
set -e
# Configuration
FUNCTION_APP_NAME="java-functions-app"
RESOURCE_GROUP="java-functions-rg"
LOCATION="eastus"
APP_SERVICE_PLAN="java-functions-asp"
STORAGE_ACCOUNT="javafuncstorage$RANDOM"
COSMOS_DB_ACCOUNT="java-func-cosmos-$RANDOM"
echo "Deploying Azure Functions Java Application..."
# Create Resource Group
echo "Creating resource group..."
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create Storage Account
echo "Creating storage account..."
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard_LRS
# Create App Service Plan
echo "Creating app service plan..."
az appservice plan create \
--name $APP_SERVICE_PLAN \
--resource-group $RESOURCE_GROUP \
--sku B1 \
--is-linux
# Create Function App
echo "Creating function app..."
az functionapp create \
--name $FUNCTION_APP_NAME \
--resource-group $RESOURCE_GROUP \
--plan $APP_SERVICE_PLAN \
--runtime java \
--runtime-version 11 \
--functions-version 4 \
--storage-account $STORAGE_ACCOUNT \
--os-type Linux
# Build and package the application
echo "Building application..."
mvn clean package
# Deploy the function app
echo "Deploying application..."
mvn azure-functions:deploy
# Configure application settings
echo "Configuring application settings..."
az functionapp config appsettings set \
--name $FUNCTION_APP_NAME \
--resource-group $RESOURCE_GROUP \
--settings \
AzureWebJobsStorage=$(az storage account show-connection-string --name $STORAGE_ACCOUNT --resource-group $RESOURCE_GROUP --query connectionString --output tsv) \
FUNCTIONS_WORKER_RUNTIME=java \
FUNCTIONS_EXTENSION_VERSION=~4
echo "Deployment completed successfully!"
echo "Function App URL: https://$FUNCTION_APP_NAME.azurewebsites.net"
3. Function App Structure
src/main/java/com/company/azure/functions/
├── UserFunctions.java          # HTTP Trigger Functions
├── BlobStorageFunctions.java   # Blob Trigger Functions  
├── QueueFunctions.java         # Queue Trigger Functions
└── TimerFunctions.java         # Timer Trigger Functions
src/main/resources/
├── host.json                   # Host configuration
└── local.settings.json         # Local development settings
target/azure-functions/
└── java-functions-app/
├── host.json
├── local.settings.json
├── com.company.azure.functions-1.0.0.jar
└── function-app-name/
├── function-name/
│   ├── function.json
│   └── sample.dat
└── extensions.csproj

Testing

1. Unit Tests
package com.company.azure.functions.test;
import com.microsoft.azure.functions.*;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.util.*;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for Azure Functions
*/
class UserFunctionsTest {
private static final Logger LOG = Logger.getLogger(UserFunctionsTest.class.getName());
@Test
void testCreateUser() {
// Setup
UserFunctions function = new UserFunctions();
String requestBody = """
{
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe"
}
""";
@SuppressWarnings("unchecked")
HttpRequestMessage<Optional<String>> request = mock(HttpRequestMessage.class);
when(request.getBody()).thenReturn(Optional.of(requestBody));
HttpResponseMessage.Builder responseBuilder = mock(HttpResponseMessage.Builder.class);
when(responseBuilder.body(any())).thenReturn(responseBuilder);
when(responseBuilder.header(anyString(), anyString())).thenReturn(responseBuilder);
when(responseBuilder.build()).thenReturn(mock(HttpResponseMessage.class));
when(request.createResponseBuilder(any(HttpStatus.class))).thenReturn(responseBuilder);
ExecutionContext context = mock(ExecutionContext.class);
when(context.getLogger()).thenReturn(LOG);
// Invoke
HttpResponseMessage response = function.createUser(request, context);
// Verify
assertNotNull(response);
verify(request).createResponseBuilder(HttpStatus.CREATED);
}
@Test
void testGetUser() {
// Setup
UserFunctions function = new UserFunctions();
// First create a user
String createRequestBody = """
{
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe"
}
""";
@SuppressWarnings("unchecked")
HttpRequestMessage<Optional<String>> createRequest = mock(HttpRequestMessage.class);
when(createRequest.getBody()).thenReturn(Optional.of(createRequestBody));
HttpResponseMessage.Builder createResponseBuilder = mock(HttpResponseMessage.Builder.class);
when(createResponseBuilder.body(any())).thenReturn(createResponseBuilder);
when(createResponseBuilder.header(anyString(), anyString())).thenReturn(createResponseBuilder);
ArgumentCaptor<String> responseBodyCaptor = ArgumentCaptor.forClass(String.class);
when(createResponseBuilder.body(responseBodyCaptor.capture())).thenReturn(createResponseBuilder);
when(createResponseBuilder.build()).thenReturn(mock(HttpResponseMessage.class));
when(createRequest.createResponseBuilder(any(HttpStatus.class))).thenReturn(createResponseBuilder);
ExecutionContext context = mock(ExecutionContext.class);
when(context.getLogger()).thenReturn(LOG);
// Create user
function.createUser(createRequest, context);
// Extract user ID from response
String responseBody = responseBodyCaptor.getValue();
String userId = extractUserIdFromResponse(responseBody);
// Now test getUser
@SuppressWarnings("unchecked")
HttpRequestMessage<Optional<String>> getRequest = mock(HttpRequestMessage.class);
when(getRequest.getBody()).thenReturn(Optional.empty());
HttpResponseMessage.Builder getResponseBuilder = mock(HttpResponseMessage.Builder.class);
when(getResponseBuilder.body(any())).thenReturn(getResponseBuilder);
when(getResponseBuilder.header(anyString(), anyString())).thenReturn(getResponseBuilder);
when(getResponseBuilder.build()).thenReturn(mock(HttpResponseMessage.class));
when(getRequest.createResponseBuilder(any(HttpStatus.class))).thenReturn(getResponseBuilder);
// Invoke getUser
HttpResponseMessage response = function.getUser(getRequest, userId, context);
// Verify
assertNotNull(response);
verify(getRequest).createResponseBuilder(HttpStatus.OK);
}
@Test
void testCreateUserWithInvalidEmail() {
// Setup
UserFunctions function = new UserFunctions();
String requestBody = """
{
"email": "",
"firstName": "John",
"lastName": "Doe"
}
""";
@SuppressWarnings("unchecked")
HttpRequestMessage<Optional<String>> request = mock(HttpRequestMessage.class);
when(request.getBody()).thenReturn(Optional.of(requestBody));
HttpResponseMessage.Builder responseBuilder = mock(HttpResponseMessage.Builder.class);
when(responseBuilder.body(any())).thenReturn(responseBuilder);
when(responseBuilder.header(anyString(), anyString())).thenReturn(responseBuilder);
when(responseBuilder.build()).thenReturn(mock(HttpResponseMessage.class));
when(request.createResponseBuilder(any(HttpStatus.class))).thenReturn(responseBuilder);
ExecutionContext context = mock(ExecutionContext.class);
when(context.getLogger()).thenReturn(LOG);
// Invoke
HttpResponseMessage response = function.createUser(request, context);
// Verify
assertNotNull(response);
verify(request).createResponseBuilder(HttpStatus.BAD_REQUEST);
}
private String extractUserIdFromResponse(String responseBody) {
// Simple extraction for test purposes
// In real scenario, parse JSON properly
if (responseBody.contains("\"id\"")) {
int start = responseBody.indexOf("\"id\":\"") + 6;
int end = responseBody.indexOf("\"", start);
return responseBody.substring(start, end);
}
return null;
}
}
class QueueFunctionsTest {
@Test
void testProcessUserRegistrationQueue() {
// Setup
QueueFunctions function = new QueueFunctions();
String queueMessage = """
{
"userId": "test-user-123",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"queuedAt": 1234567890
}
""";
@SuppressWarnings("unchecked")
OutputBinding<String> output = mock(OutputBinding.class);
@SuppressWarnings("unchecked")
OutputBinding<String> cosmosOutput = mock(OutputBinding.class);
ExecutionContext context = mock(ExecutionContext.class);
when(context.getLogger()).thenReturn(Logger.getGlobal());
// Invoke
function.processUserRegistrationQueue(queueMessage, output, cosmosOutput, context);
// Verify
verify(output).setValue(anyString());
verify(cosmosOutput).setValue(anyString());
}
}
2. Integration Tests
package com.company.azure.functions.test;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.*;
/**
* Integration tests for Azure Functions
*/
class UserFunctionsIntegrationTest {
private UserFunctions userFunctions;
private ExecutionContext context;
@BeforeEach
void setUp() {
userFunctions = new UserFunctions();
context = new ExecutionContext() {
@Override
public Logger getLogger() {
return Logger.getGlobal();
}
@Override
public String getInvocationId() {
return "test-invocation-id";
}
@Override
public String getFunctionName() {
return "test-function";
}
};
}
@Test
void testCreateAndGetUserIntegration() {
// Create user
String createRequestBody = """
{
"email": "[email protected]",
"firstName": "Integration",
"lastName": "Test"
}
""";
TestHttpRequestMessage createRequest = new TestHttpRequestMessage(
Optional.of(createRequestBody),
"POST",
"/api/users"
);
HttpResponseMessage createResponse = userFunctions.createUser(createRequest, context);
assertEquals(HttpStatus.CREATED, createResponse.getStatus());
// Extract user ID from response (simplified)
String responseBody = createResponse.getBody().toString();
assertTrue(responseBody.contains("[email protected]"));
}
}
// Test implementation for HTTP request
class TestHttpRequestMessage implements com.microsoft.azure.functions.HttpRequestMessage<Optional<String>> {
private final Optional<String> body;
private final String method;
private final String uri;
public TestHttpRequestMessage(Optional<String> body, String method, String uri) {
this.body = body;
this.method = method;
this.uri = uri;
}
@Override
public Optional<String> getBody() {
return body;
}
@Override
public String getUri() {
return uri;
}
@Override
public String getMethod() {
return method;
}
// Implement other methods with default values or empty implementations
@Override
public Map<String, String> getHeaders() {
return new HashMap<>();
}
@Override
public Map<String, String> getQueryParameters() {
return new HashMap<>();
}
@Override
public HttpResponseMessage.Builder createResponseBuilder(HttpStatus status) {
return new TestHttpResponseMessage.Builder(status);
}
// Other methods can throw UnsupportedOperationException or return default values
@Override
public Object getAttribute(String name) {
throw new UnsupportedOperationException();
}
@Override
public Set<String> getAttributeNames() {
throw new UnsupportedOperationException();
}
}
class TestHttpResponseMessage implements HttpResponseMessage {
private final HttpStatus status;
private final Object body;
private final Map<String, String> headers;
public TestHttpResponseMessage(HttpStatus status, Object body, Map<String, String> headers) {
this.status = status;
this.body = body;
this.headers = headers;
}
@Override
public HttpStatus getStatus() {
return status;
}
@Override
public String getHeader(String key) {
return headers.get(key);
}
@Override
public Object getBody() {
return body;
}
static class Builder implements HttpResponseMessage.Builder {
private HttpStatus status;
private Object body;
private final Map<String, String> headers = new HashMap<>();
public Builder(HttpStatus status) {
this.status = status;
}
@Override
public Builder status(HttpStatus status) {
this.status = status;
return this;
}
@Override
public Builder header(String key, String value) {
headers.put(key, value);
return this;
}
@Override
public Builder body(Object body) {
this.body = body;
return this;
}
@Override
public HttpResponseMessage build() {
return new TestHttpResponseMessage(status, body, headers);
}
}
}

Summary

This Azure Functions Java implementation provides:

  1. Multiple Trigger Types: HTTP, Blob Storage, Queue Storage, Timer
  2. Input/Output Bindings: Seamless integration with Azure services
  3. Scalable Architecture: Automatic scaling based on workload
  4. Comprehensive Testing: Unit and integration tests
  5. Production Ready: Error handling, logging, monitoring

Key Benefits:

  • Serverless: No infrastructure management
  • Cost Effective: Pay only for execution time
  • Integrated: Native integration with Azure ecosystem
  • Java Ecosystem: Leverage existing Java libraries and tools
  • Enterprise Ready: Security, monitoring, and compliance features

This implementation demonstrates how to build robust, scalable serverless applications using Azure Functions with Java, covering common patterns and best practices for production deployments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper