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:
- Multiple Trigger Types: HTTP, Blob Storage, Queue Storage, Timer
- Input/Output Bindings: Seamless integration with Azure services
- Scalable Architecture: Automatic scaling based on workload
- Comprehensive Testing: Unit and integration tests
- 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.