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.