Overview
Google Cloud Functions is a serverless execution environment for building and connecting cloud services. With Cloud Functions, you write simple, single-purpose functions that are attached to events emitted from your cloud infrastructure and services.
Key Features
- Serverless: No infrastructure management
- Event-driven: Trigger functions via HTTP, Cloud Storage, Pub/Sub, etc.
- Auto-scaling: Automatically scales based on load
- Pay-per-use: Only pay for execution time
- Multiple Runtimes: Java 11, Java 17, Java 21 support
Setup and Configuration
1. Maven Configuration
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>cloud-functions</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<function.maven.plugin.version>0.10.0</function.maven.plugin.version>
<google.cloud.functions.version>1.0.4</google.cloud.functions.version>
</properties>
<dependencies>
<!-- Cloud Functions API -->
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>${google.cloud.functions.version}</version>
<scope>provided</scope>
</dependency>
<!-- GCP Libraries -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-pubsub</artifactId>
<version>1.123.0</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
<version>3.9.0</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>${function.maven.plugin.version}</version>
<configuration>
<functionTarget>com.example.HelloHttp</functionTarget>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. Gradle Configuration
plugins {
id 'java'
id 'com.google.cloud.functions' version '0.10.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.cloud.functions:functions-framework-api:1.0.4'
implementation 'com.google.cloud:google-cloud-storage:2.20.0'
implementation 'com.google.cloud:google-cloud-pubsub:1.123.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
functions {
runtimeJava = '11'
}
HTTP Functions
1. Basic HTTP Function
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.logging.Logger;
public class HelloHttp implements HttpFunction {
private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());
private static final Gson gson = new Gson();
@Override
public void service(HttpRequest request, HttpResponse response) throws IOException {
BufferedWriter writer = response.getWriter();
// Set response type
response.setContentType("application/json");
// Get request method
String method = request.getMethod();
switch (method) {
case "GET":
handleGet(request, response, writer);
break;
case "POST":
handlePost(request, response, writer);
break;
case "PUT":
handlePut(request, response, writer);
break;
case "DELETE":
handleDelete(request, response, writer);
break;
default:
response.setStatusCode(405);
writer.write("{\"error\": \"Method not allowed\"}");
}
}
private void handleGet(HttpRequest request, HttpResponse response, BufferedWriter writer)
throws IOException {
String name = request.getFirstQueryParameter("name").orElse("World");
JsonObject responseJson = new JsonObject();
responseJson.addProperty("message", "Hello " + name);
responseJson.addProperty("timestamp", System.currentTimeMillis());
writer.write(gson.toJson(responseJson));
}
private void handlePost(HttpRequest request, HttpResponse response, BufferedWriter writer)
throws IOException {
try {
// Parse JSON body
JsonObject body = gson.fromJson(request.getReader(), JsonObject.class);
String name = body.has("name") ? body.get("name").getAsString() : "World";
JsonObject responseJson = new JsonObject();
responseJson.addProperty("message", "Hello " + name);
responseJson.addProperty("received", true);
responseJson.addProperty("timestamp", System.currentTimeMillis());
writer.write(gson.toJson(responseJson));
} catch (Exception e) {
response.setStatusCode(400);
writer.write("{\"error\": \"Invalid JSON\"}");
}
}
private void handlePut(HttpRequest request, HttpResponse response, BufferedWriter writer)
throws IOException {
response.setStatusCode(501);
writer.write("{\"error\": \"Not implemented\"}");
}
private void handleDelete(HttpRequest request, HttpResponse response, BufferedWriter writer)
throws IOException {
response.setStatusCode(501);
writer.write("{\"error\": \"Not implemented\"}");
}
}
2. Advanced HTTP Function with Dependency Injection
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.BufferedWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Optional;
import java.util.logging.Logger;
public class UserServiceFunction implements HttpFunction {
private static final Logger logger = Logger.getLogger(UserServiceFunction.class.getName());
private static final Gson gson = new Gson();
// Database configuration - use Cloud SQL or environment variables
private final String dbUrl = System.getenv("DB_URL");
private final String dbUser = System.getenv("DB_USER");
private final String dbPassword = System.getenv("DB_PASSWORD");
@Override
public void service(HttpRequest request, HttpResponse response) throws IOException {
String path = request.getPath();
String method = request.getMethod();
logger.info("Processing " + method + " " + path);
try {
switch (path) {
case "/users":
handleUsersEndpoint(request, response);
break;
case "/users/profile":
handleUserProfile(request, response);
break;
default:
response.setStatusCode(404);
response.getWriter().write("{\"error\": \"Not found\"}");
}
} catch (Exception e) {
logger.severe("Error processing request: " + e.getMessage());
response.setStatusCode(500);
response.getWriter().write("{\"error\": \"Internal server error\"}");
}
}
private void handleUsersEndpoint(HttpRequest request, HttpResponse response)
throws IOException {
switch (request.getMethod()) {
case "GET":
getUsers(request, response);
break;
case "POST":
createUser(request, response);
break;
default:
response.setStatusCode(405);
response.getWriter().write("{\"error\": \"Method not allowed\"}");
}
}
private void getUsers(HttpRequest request, HttpResponse response) throws IOException {
try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) {
String limit = request.getFirstQueryParameter("limit").orElse("10");
String offset = request.getFirstQueryParameter("offset").orElse("0");
String sql = "SELECT id, name, email, created_at FROM users LIMIT ? OFFSET ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, Integer.parseInt(limit));
stmt.setInt(2, Integer.parseInt(offset));
ResultSet rs = stmt.executeQuery();
JsonObject responseJson = new JsonObject();
JsonObject usersArray = new JsonObject();
while (rs.next()) {
JsonObject user = new JsonObject();
user.addProperty("id", rs.getInt("id"));
user.addProperty("name", rs.getString("name"));
user.addProperty("email", rs.getString("email"));
user.addProperty("created_at", rs.getTimestamp("created_at").toString());
usersArray.add(String.valueOf(rs.getInt("id")), user);
}
responseJson.add("users", usersArray);
responseJson.addProperty("count", usersArray.size());
response.getWriter().write(gson.toJson(responseJson));
} catch (Exception e) {
logger.severe("Database error: " + e.getMessage());
response.setStatusCode(500);
response.getWriter().write("{\"error\": \"Database error\"}");
}
}
private void createUser(HttpRequest request, HttpResponse response) throws IOException {
try {
JsonObject body = gson.fromJson(request.getReader(), JsonObject.class);
String name = body.get("name").getAsString();
String email = body.get("email").getAsString();
try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
stmt.setString(1, name);
stmt.setString(2, email);
int affectedRows = stmt.executeUpdate();
if (affectedRows > 0) {
ResultSet generatedKeys = stmt.getGeneratedKeys();
if (generatedKeys.next()) {
int userId = generatedKeys.getInt(1);
JsonObject responseJson = new JsonObject();
responseJson.addProperty("id", userId);
responseJson.addProperty("name", name);
responseJson.addProperty("email", email);
responseJson.addProperty("message", "User created successfully");
response.setStatusCode(201);
response.getWriter().write(gson.toJson(responseJson));
}
} else {
response.setStatusCode(400);
response.getWriter().write("{\"error\": \"Failed to create user\"}");
}
}
} catch (Exception e) {
logger.severe("Error creating user: " + e.getMessage());
response.setStatusCode(400);
response.getWriter().write("{\"error\": \"Invalid request\"}");
}
}
private void handleUserProfile(HttpRequest request, HttpResponse response) throws IOException {
// Implementation for user profile endpoint
response.setStatusCode(501);
response.getWriter().write("{\"error\": \"Not implemented\"}");
}
}
Background Functions (Pub/Sub)
1. Pub/Sub Function
import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import com.google.cloud.pubsub.v1.Publisher;
import com.google.protobuf.ByteString;
import com.google.pubsub.v1.PubsubMessage;
import com.google.pubsub.v1.TopicName;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
public class PubSubFunction implements BackgroundFunction<PubSubFunction.PubSubMessage> {
private static final Logger logger = Logger.getLogger(PubSubFunction.class.getName());
private static final Gson gson = new Gson();
// Environment variables
private final String projectId = System.getenv("GCP_PROJECT_ID");
private final String topicId = System.getenv("PROCESSED_MESSAGES_TOPIC");
public static class PubSubMessage {
private String data;
private Map<String, String> attributes;
private String messageId;
private String publishTime;
// Getters and setters
public String getData() { return data; }
public void setData(String data) { this.data = data; }
public Map<String, String> getAttributes() { return attributes; }
public void setAttributes(Map<String, String> attributes) { this.attributes = attributes; }
public String getMessageId() { return messageId; }
public void setMessageId(String messageId) { this.messageId = messageId; }
public String getPublishTime() { return publishTime; }
public void setPublishTime(String publishTime) { this.publishTime = publishTime; }
}
@Override
public void accept(PubSubMessage message, Context context) throws Exception {
logger.info("Processing message: " + message.getMessageId());
try {
// Decode base64 data
String decodedData = new String(
java.util.Base64.getDecoder().decode(message.getData()),
StandardCharsets.UTF_8
);
// Parse JSON message
JsonObject jsonMessage = gson.fromJson(decodedData, JsonObject.class);
// Process the message
processMessage(jsonMessage, message.getAttributes());
// Publish result to another topic
publishProcessedMessage(jsonMessage, message.getMessageId());
logger.info("Successfully processed message: " + message.getMessageId());
} catch (Exception e) {
logger.severe("Error processing message " + message.getMessageId() + ": " + e.getMessage());
throw e; // Message will be retried
}
}
private void processMessage(JsonObject message, Map<String, String> attributes) {
String messageType = attributes.getOrDefault("type", "unknown");
switch (messageType) {
case "user_registration":
processUserRegistration(message);
break;
case "order_created":
processOrder(message);
break;
case "payment_processed":
processPayment(message);
break;
default:
logger.warning("Unknown message type: " + messageType);
}
}
private void processUserRegistration(JsonObject message) {
String userId = message.get("user_id").getAsString();
String email = message.get("email").getAsString();
logger.info("Processing user registration: " + userId + ", email: " + email);
// Add user to database, send welcome email, etc.
// Simulate processing time
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Completed user registration for: " + userId);
}
private void processOrder(JsonObject message) {
String orderId = message.get("order_id").getAsString();
double amount = message.get("amount").getAsDouble();
logger.info("Processing order: " + orderId + ", amount: " + amount);
// Update inventory, calculate taxes, etc.
}
private void processPayment(JsonObject message) {
String paymentId = message.get("payment_id").getAsString();
String status = message.get("status").getAsString();
logger.info("Processing payment: " + paymentId + ", status: " + status);
// Update order status, send notifications, etc.
}
private void publishProcessedMessage(JsonObject originalMessage, String originalMessageId) {
try {
TopicName topicName = TopicName.of(projectId, topicId);
Publisher publisher = Publisher.newBuilder(topicName).build();
JsonObject processedMessage = new JsonObject();
processedMessage.addProperty("original_message_id", originalMessageId);
processedMessage.addProperty("processed_at", System.currentTimeMillis());
processedMessage.add("original_data", originalMessage);
processedMessage.addProperty("status", "processed");
String messageData = gson.toJson(processedMessage);
ByteString data = ByteString.copyFromUtf8(messageData);
PubsubMessage pubsubMessage = PubsubMessage.newBuilder()
.setData(data)
.putAttributes("processor", "java-cloud-function")
.putAttributes("processing_time", String.valueOf(System.currentTimeMillis()))
.build();
publisher.publish(pubsubMessage);
publisher.shutdown();
logger.info("Published processed message for: " + originalMessageId);
} catch (Exception e) {
logger.severe("Failed to publish processed message: " + e.getMessage());
}
}
}
2. Cloud Storage Function
import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
public class StorageFunction implements BackgroundFunction<StorageFunction.StorageEvent> {
private static final Logger logger = Logger.getLogger(StorageFunction.class.getName());
private static final Gson gson = new Gson();
private static final Storage storage = StorageOptions.getDefaultInstance().getService();
private final String processedBucket = System.getenv("PROCESSED_BUCKET");
public static class StorageEvent {
private String bucket;
private String name;
private String metageneration;
private String timeCreated;
private String updated;
// Getters and setters
public String getBucket() { return bucket; }
public void setBucket(String bucket) { this.bucket = bucket; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getMetageneration() { return metageneration; }
public void setMetageneration(String metageneration) { this.metageneration = metageneration; }
public String getTimeCreated() { return timeCreated; }
public void setTimeCreated(String timeCreated) { this.timeCreated = timeCreated; }
public String getUpdated() { return updated; }
public void setUpdated(String updated) { this.updated = updated; }
}
@Override
public void accept(StorageEvent event, Context context) throws Exception {
logger.info("Processing file: " + event.getName() + " from bucket: " + event.getBucket());
try {
// Get the file from Cloud Storage
Blob blob = storage.get(BlobId.of(event.getBucket(), event.getName()));
if (blob == null) {
logger.warning("File not found: " + event.getName());
return;
}
String content = new String(blob.getContent(), StandardCharsets.UTF_8);
String fileExtension = getFileExtension(event.getName());
// Process based on file type
switch (fileExtension) {
case "json":
processJsonFile(content, event);
break;
case "csv":
processCsvFile(content, event);
break;
case "txt":
processTextFile(content, event);
break;
default:
logger.warning("Unsupported file type: " + fileExtension);
}
logger.info("Successfully processed file: " + event.getName());
} catch (Exception e) {
logger.severe("Error processing file " + event.getName() + ": " + e.getMessage());
throw e;
}
}
private void processJsonFile(String content, StorageEvent event) {
try {
JsonObject json = gson.fromJson(content, JsonObject.class);
// Add processing metadata
json.addProperty("processed_at", System.currentTimeMillis());
json.addProperty("processed_by", "storage-function");
json.addProperty("original_file", event.getName());
// Save processed file to another bucket
String processedFileName = "processed/" + event.getName();
BlobId blobId = BlobId.of(processedBucket, processedFileName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType("application/json")
.build();
storage.create(blobInfo, gson.toJson(json).getBytes(StandardCharsets.UTF_8));
logger.info("Processed JSON file saved to: " + processedFileName);
} catch (Exception e) {
throw new RuntimeException("Failed to process JSON file", e);
}
}
private void processCsvFile(String content, StorageEvent event) {
try {
// Simple CSV processing - convert to JSON
String[] lines = content.split("\n");
if (lines.length == 0) return;
String[] headers = lines[0].split(",");
JsonObject result = new JsonObject();
JsonObject rows = new JsonObject();
for (int i = 1; i < lines.length; i++) {
String[] values = lines[i].split(",");
JsonObject row = new JsonObject();
for (int j = 0; j < headers.length && j < values.length; j++) {
row.addProperty(headers[j].trim(), values[j].trim());
}
rows.add("row_" + i, row);
}
result.add("headers", gson.toJsonTree(headers));
result.add("rows", rows);
result.addProperty("row_count", lines.length - 1);
result.addProperty("processed_at", System.currentTimeMillis());
// Save as JSON
String processedFileName = "processed/" + event.getName().replace(".csv", ".json");
BlobId blobId = BlobId.of(processedBucket, processedFileName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType("application/json")
.build();
storage.create(blobInfo, gson.toJson(result).getBytes(StandardCharsets.UTF_8));
logger.info("Converted CSV to JSON: " + processedFileName);
} catch (Exception e) {
throw new RuntimeException("Failed to process CSV file", e);
}
}
private void processTextFile(String content, StorageEvent event) {
// Simple text processing - word count
String[] words = content.split("\\s+");
int wordCount = words.length;
int charCount = content.length();
JsonObject stats = new JsonObject();
stats.addProperty("file_name", event.getName());
stats.addProperty("word_count", wordCount);
stats.addProperty("character_count", charCount);
stats.addProperty("processed_at", System.currentTimeMillis());
// Save statistics
String statsFileName = "stats/" + event.getName().replace(".txt", "_stats.json");
BlobId blobId = BlobId.of(processedBucket, statsFileName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId)
.setContentType("application/json")
.build();
storage.create(blobInfo, gson.toJson(stats).getBytes(StandardCharsets.UTF_8));
logger.info("Text file statistics saved: " + statsFileName);
}
private String getFileExtension(String filename) {
int lastDot = filename.lastIndexOf(".");
return lastDot > 0 ? filename.substring(lastDot + 1).toLowerCase() : "";
}
}
Firestore Functions
1. Firestore Trigger Function
import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import com.google.cloud.firestore.DocumentSnapshot;
import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.events.cloud.firestore.v1.Document;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.logging.Logger;
public class FirestoreFunction implements BackgroundFunction<DocumentEventData> {
private static final Logger logger = Logger.getLogger(FirestoreFunction.class.getName());
private static final Gson gson = new Gson();
private static final Firestore firestore = FirestoreOptions.getDefaultInstance().getService();
@Override
public void accept(DocumentEventData event, Context context) throws Exception {
String resource = context.resource();
logger.info("Processing Firestore event for: " + resource);
Document document = event.getValue();
String operation = event.getOldValue() != null ?
(document != null ? "UPDATE" : "DELETE") : "CREATE";
switch (operation) {
case "CREATE":
handleCreate(document, resource);
break;
case "UPDATE":
handleUpdate(document, event.getOldValue(), resource);
break;
case "DELETE":
handleDelete(event.getOldValue(), resource);
break;
}
}
private void handleCreate(Document document, String resource) {
try {
String documentPath = extractDocumentPath(resource);
JsonObject documentData = convertDocumentToJson(document);
logger.info("New document created: " + documentPath);
logger.info("Document data: " + documentData);
// Example: Process user registration
if (documentPath.contains("users")) {
processNewUser(documentData, documentPath);
}
// Example: Process new order
else if (documentPath.contains("orders")) {
processNewOrder(documentData, documentPath);
}
} catch (Exception e) {
logger.severe("Error handling document creation: " + e.getMessage());
}
}
private void handleUpdate(Document newDocument, Document oldDocument, String resource) {
try {
String documentPath = extractDocumentPath(resource);
JsonObject newData = convertDocumentToJson(newDocument);
JsonObject oldData = convertDocumentToJson(oldDocument);
logger.info("Document updated: " + documentPath);
// Check what changed
for (String key : newData.keySet()) {
if (!newData.get(key).equals(oldData.get(key))) {
logger.info("Field changed: " + key + " from " +
oldData.get(key) + " to " + newData.get(key));
}
}
// Example: Update user statistics
if (documentPath.contains("users")) {
updateUserStats(newData, documentPath);
}
} catch (Exception e) {
logger.severe("Error handling document update: " + e.getMessage());
}
}
private void handleDelete(Document oldDocument, String resource) {
try {
String documentPath = extractDocumentPath(resource);
JsonObject oldData = convertDocumentToJson(oldDocument);
logger.info("Document deleted: " + documentPath);
// Example: Archive deleted document
archiveDocument(documentPath, oldData);
} catch (Exception e) {
logger.severe("Error handling document deletion: " + e.getMessage());
}
}
private void processNewUser(JsonObject userData, String documentPath) {
try {
String userId = extractDocumentId(documentPath);
String email = userData.get("email").getAsString();
String name = userData.get("name").getAsString();
// Create user profile in another collection
JsonObject userProfile = new JsonObject();
userProfile.addProperty("user_id", userId);
userProfile.addProperty("email", email);
userProfile.addProperty("name", name);
userProfile.addProperty("created_at", System.currentTimeMillis());
userProfile.addProperty("status", "active");
firestore.collection("user_profiles")
.document(userId)
.set(userProfile)
.get();
logger.info("Created user profile for: " + userId);
} catch (Exception e) {
logger.severe("Error processing new user: " + e.getMessage());
}
}
private void processNewOrder(JsonObject orderData, String documentPath) {
try {
String orderId = extractDocumentId(documentPath);
double amount = orderData.get("amount").getAsDouble();
String userId = orderData.get("user_id").getAsString();
// Update user's total spending
firestore.runTransaction(transaction -> {
var userRef = firestore.collection("users").document(userId);
var userDoc = transaction.get(userRef);
if (userDoc.exists()) {
double currentTotal = userDoc.getDouble("total_spent");
double newTotal = currentTotal + amount;
transaction.update(userRef, "total_spent", newTotal);
transaction.update(userRef, "last_order_at", System.currentTimeMillis());
}
return null;
}).get();
logger.info("Updated user spending for order: " + orderId);
} catch (Exception e) {
logger.severe("Error processing new order: " + e.getMessage());
}
}
private void updateUserStats(JsonObject userData, String documentPath) {
// Implementation for updating user statistics
}
private void archiveDocument(String documentPath, JsonObject documentData) {
try {
String documentId = extractDocumentId(documentPath);
String collection = extractCollectionName(documentPath);
JsonObject archiveData = new JsonObject();
archiveData.addProperty("original_path", documentPath);
archiveData.addProperty("archived_at", System.currentTimeMillis());
archiveData.add("original_data", documentData);
firestore.collection("archived_" + collection)
.document(documentId)
.set(archiveData)
.get();
logger.info("Archived document: " + documentPath);
} catch (Exception e) {
logger.severe("Error archiving document: " + e.getMessage());
}
}
private String extractDocumentPath(String resource) {
// Extract document path from resource string
String[] parts = resource.split("/documents/");
return parts.length > 1 ? parts[1] : resource;
}
private String extractCollectionName(String documentPath) {
String[] parts = documentPath.split("/");
return parts.length > 0 ? parts[0] : "unknown";
}
private String extractDocumentId(String documentPath) {
String[] parts = documentPath.split("/");
return parts.length > 1 ? parts[parts.length - 1] : "unknown";
}
private JsonObject convertDocumentToJson(Document document) {
if (document == null) return new JsonObject();
JsonObject json = new JsonObject();
var fields = document.getFieldsMap();
for (var entry : fields.entrySet()) {
String key = entry.getKey();
var value = entry.getValue();
// Convert based on value type
if (value.hasStringValue()) {
json.addProperty(key, value.getStringValue());
} else if (value.hasIntegerValue()) {
json.addProperty(key, value.getIntegerValue());
} else if (value.hasDoubleValue()) {
json.addProperty(key, value.getDoubleValue());
} else if (value.hasBooleanValue()) {
json.addProperty(key, value.getBooleanValue());
}
// Add more type conversions as needed
}
return json;
}
}
Advanced Patterns
1. Function with External API Calls
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
public class ApiGatewayFunction implements HttpFunction {
private static final Logger logger = Logger.getLogger(ApiGatewayFunction.class.getName());
private static final Gson gson = new Gson();
private static final ExecutorService executor = Executors.newCachedThreadPool();
private final String externalApiKey = System.getenv("EXTERNAL_API_KEY");
@Override
public void service(HttpRequest request, HttpResponse response) throws IOException {
String path = request.getPath();
switch (path) {
case "/weather":
getWeather(request, response);
break;
case "/geocode":
geocodeAddress(request, response);
break;
case "/currency":
convertCurrency(request, response);
break;
default:
response.setStatusCode(404);
response.getWriter().write("{\"error\": \"Endpoint not found\"}");
}
}
private void getWeather(HttpRequest request, HttpResponse response) throws IOException {
String city = request.getFirstQueryParameter("city").orElse("London");
executor.submit(() -> {
try {
String weatherData = callWeatherApi(city);
JsonObject responseJson = gson.fromJson(weatherData, JsonObject.class);
// Process and format response
JsonObject formattedResponse = new JsonObject();
formattedResponse.addProperty("city", city);
formattedResponse.addProperty("temperature",
responseJson.getAsJsonObject("main").get("temp").getAsDouble());
formattedResponse.addProperty("description",
responseJson.getAsJsonArray("weather").get(0).getAsJsonObject()
.get("description").getAsString());
response.setContentType("application/json");
response.getWriter().write(gson.toJson(formattedResponse));
} catch (Exception e) {
logger.severe("Weather API error: " + e.getMessage());
try {
response.setStatusCode(500);
response.getWriter().write("{\"error\": \"Weather service unavailable\"}");
} catch (IOException ioException) {
logger.severe("Failed to send error response: " + ioException.getMessage());
}
}
});
}
private void geocodeAddress(HttpRequest request, HttpResponse response) throws IOException {
String address = request.getFirstQueryParameter("address").orElse("");
if (address.isEmpty()) {
response.setStatusCode(400);
response.getWriter().write("{\"error\": \"Address parameter required\"}");
return;
}
executor.submit(() -> {
try {
String geocodeData = callGeocodeApi(address);
JsonObject responseJson = gson.fromJson(geocodeData, JsonObjectObject);
// Process geocoding results
JsonObject result = new JsonObject();
result.addProperty("address", address);
if (responseJson.has("results") && responseJson.getAsJsonArray("results").size() > 0) {
JsonObject firstResult = responseJson.getAsJsonArray("results").get(0).getAsJsonObject();
JsonObject location = firstResult.getAsJsonObject("geometry").getAsJsonObject("location");
result.addProperty("latitude", location.get("lat").getAsDouble());
result.addProperty("longitude", location.get("lng").getAsDouble());
result.addProperty("formatted_address", firstResult.get("formatted_address").getAsString());
}
response.setContentType("application/json");
response.getWriter().write(gson.toJson(result));
} catch (Exception e) {
logger.severe("Geocoding API error: " + e.getMessage());
try {
response.setStatusCode(500);
response.getWriter().write("{\"error\": \"Geocoding service unavailable\"}");
} catch (IOException ioException) {
logger.severe("Failed to send error response: " + ioException.getMessage());
}
}
});
}
private void convertCurrency(HttpRequest request, HttpResponse response) throws IOException {
String from = request.getFirstQueryParameter("from").orElse("USD");
String to = request.getFirstQueryParameter("to").orElse("EUR");
String amountStr = request.getFirstQueryParameter("amount").orElse("1.0");
try {
double amount = Double.parseDouble(amountStr);
executor.submit(() -> {
try {
String exchangeData = callCurrencyApi(from, to);
JsonObject responseJson = gson.fromJson(exchangeData, JsonObject);
double rate = responseJson.getAsJsonObject("rates").get(to).getAsDouble();
double convertedAmount = amount * rate;
JsonObject result = new JsonObject();
result.addProperty("from", from);
result.addProperty("to", to);
result.addProperty("amount", amount);
result.addProperty("converted_amount", convertedAmount);
result.addProperty("exchange_rate", rate);
result.addProperty("timestamp", System.currentTimeMillis());
response.setContentType("application/json");
response.getWriter().write(gson.toJson(result));
} catch (Exception e) {
logger.severe("Currency API error: " + e.getMessage());
try {
response.setStatusCode(500);
response.getWriter().write("{\"error\": \"Currency service unavailable\"}");
} catch (IOException ioException) {
logger.severe("Failed to send error response: " + ioException.getMessage());
}
}
});
} catch (NumberFormatException e) {
response.setStatusCode(400);
response.getWriter().write("{\"error\": \"Invalid amount parameter\"}");
}
}
private String callWeatherApi(String city) throws IOException {
String urlString = String.format(
"https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric",
city, externalApiKey
);
return callExternalApi(urlString);
}
private String callGeocodeApi(String address) throws IOException {
String urlString = String.format(
"https://maps.googleapis.com/maps/api/geocode/json?address=%s&key=%s",
java.net.URLEncoder.encode(address, "UTF-8"), externalApiKey
);
return callExternalApi(urlString);
}
private String callCurrencyApi(String from, String to) throws IOException {
String urlString = String.format(
"https://api.exchangerate-api.com/v4/latest/%s",
from
);
return callExternalApi(urlString);
}
private String callExternalApi(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(10000);
try {
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
Scanner scanner = new Scanner(connection.getInputStream());
StringBuilder response = new StringBuilder();
while (scanner.hasNextLine()) {
response.append(scanner.nextLine());
}
scanner.close();
return response.toString();
} else {
throw new IOException("HTTP error code: " + responseCode);
}
} finally {
connection.disconnect();
}
}
}
Deployment and Configuration
1. Deployment Script
#!/bin/bash
# deploy.sh - Deployment script for Google Cloud Functions
PROJECT_ID="your-project-id"
REGION="us-central1"
RUNTIME="java11"
MEMORY="512MB"
TIMEOUT="60s"
# Deploy HTTP Functions
deploy_http_function() {
local FUNCTION_NAME=$1
local ENTRY_POINT=$2
local TRIGGER_FLAG=$3
echo "Deploying HTTP function: $FUNCTION_NAME"
gcloud functions deploy $FUNCTION_NAME \
--project=$PROJECT_ID \
--region=$REGION \
--runtime=$RUNTIME \
--memory=$MEMORY \
--timeout=$TIMEOUT \
--trigger-http \
--entry-point=$ENTRY_POINT \
--allow-unauthenticated \
--source=.
}
# Deploy Background Functions
deploy_background_function() {
local FUNCTION_NAME=$1
local ENTRY_POINT=$2
local TRIGGER_TYPE=$3
local TRIGGER_RESOURCE=$4
echo "Deploying background function: $FUNCTION_NAME"
gcloud functions deploy $FUNCTION_NAME \
--project=$PROJECT_ID \
--region=$REGION \
--runtime=$RUNTIME \
--memory=$MEMORY \
--timeout=$TIMEOUT \
--trigger-$TRIGGER_TYPE=$TRIGGER_RESOURCE \
--entry-point=$ENTRY_POINT \
--source=.
}
# Deploy all functions
echo "Starting deployment..."
# HTTP Functions
deploy_http_function "hello-http" "com.example.HelloHttp"
deploy_http_function "user-service" "com.example.UserServiceFunction"
deploy_http_function "api-gateway" "com.example.ApiGatewayFunction"
# Pub/Sub Functions
deploy_background_function "pubsub-processor" "com.example.PubSubFunction" \
"topic" "message-topic"
# Storage Functions
deploy_background_function "storage-processor" "com.example.StorageFunction" \
"bucket" "input-bucket"
# Firestore Functions
deploy_background_function "firestore-listener" "com.example.FirestoreFunction" \
"event" "projects/$PROJECT_ID/databases/(default)/documents/users/{userId}"
echo "Deployment completed!"
2. Environment Configuration
# env.yaml DB_URL: "jdbc:postgresql://localhost:5432/mydatabase" DB_USER: "postgres" DB_PASSWORD: "secret" GCP_PROJECT_ID: "my-project" PROCESSED_MESSAGES_TOPIC: "processed-messages" PROCESSED_BUCKET: "processed-files" EXTERNAL_API_KEY: "your-api-key" LOG_LEVEL: "INFO" # Deployment command with environment variables gcloud functions deploy my-function \ --env-vars-file env.yaml \ --update-env-vars "NEW_VAR=value"
This comprehensive guide covers Google Cloud Functions in Java with examples for HTTP functions, background functions (Pub/Sub, Cloud Storage, Firestore), advanced patterns, and deployment configurations.