OpenFaaS (Functions as a Service) is a framework for building serverless functions with Docker and Kubernetes. Java templates for OpenFaaS enable you to run Java functions in a serverless environment with minimal overhead and fast cold starts.
What is OpenFaaS?
OpenFaaS makes it simple to deploy both functions and existing code to Kubernetes. Key features:
- Container-native functions
- Simple scaling and auto-scaling
- Built-in UI and CLI
- Multi-language support including Java
- Enterprise-ready with async processing
OpenFaaS Java Architecture
[Java Function] → [OpenFaaS Template] → [Docker Image] → [OpenFaaS Gateway] → [Kubernetes] | | | | | Business logic Build wrapper Containerized API Gateway & Orchestration & dependencies function scaling engine platform
Hands-On Tutorial: Building Custom OpenFaaS Java Templates
Let's build comprehensive Java templates for OpenFaaS supporting various Java frameworks and use cases.
Step 1: Project Structure Setup
openfaas-java-templates/ ├── templates/ │ ├── java11-maven/ │ ├── java17-gradle/ │ ├── java21-spring-native/ │ ├── quarkus-native/ │ └── micronaut-function/ ├── functions/ │ ├── user-service/ │ ├── image-processor/ │ ├── data-transformer/ │ └── api-gateway/ ├── build-scripts/ └── kubernetes/
Step 2: Base Java Template Configuration
templates/java17-gradle/template.yml:
language: java17-gradle platform: x86_64 repository: https://github.com/openfaas/templates official: true description: Java 17 with Gradle template for OpenFaaS keywords: - java - gradle - openjdk - serverless - functions readme: | # Java 17 Gradle Template This template uses Java 17 with Gradle for building OpenFaaS functions. Features: - Java 17 LTS - Gradle build system - Fast cold starts with optimized JVM flags - Health checks and metrics - Multi-stage Docker build
templates/java17-gradle/Dockerfile:
# Multi-stage build for optimal image size FROM gradle:7.6-jdk17-alpine as builder WORKDIR /home/app COPY build.gradle settings.gradle ./ COPY src ./src RUN gradle build --no-daemon -x test # Runtime stage FROM openjdk:17-jre-slim # Install necessary packages RUN apt-get update && \ apt-get install -y --no-install-recommends curl && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean # Create non-root user RUN addgroup --system app && adduser --system --ingroup app app WORKDIR /home/app COPY --from=builder /home/app/build/libs/*.jar app.jar COPY --from=builder /home/app/function/function.jar function.jar # OpenFaaS health check HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/_/health || exit 1 # JVM optimization for serverless ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xss256k -Djava.security.egd=file:/dev/./urandom" USER app EXPOSE 8080 CMD ["sh", "-c", "java $JAVA_OPTS -jar function.jar"]
templates/java17-gradle/build.gradle:
plugins { id 'java' id 'com.github.johnrengelman.shadow' version '8.1.1' } group = 'com.openfaas.function' version = '1.0.0' sourceCompatibility = '17' repositories { mavenCentral() } dependencies { // OpenFaaS function API implementation 'com.openfaas:model:1.0.2' implementation 'com.openfaas:entrypoint:1.0.2' // HTTP server implementation 'com.sun.net.httpserver:http:20070405' // JSON processing implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' // Logging implementation 'org.slf4j:slf4j-simple:2.0.7' // Utilities implementation 'org.apache.commons:commons-lang3:3.13.0' implementation 'commons-io:commons-io:2.13.0' // Testing testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testImplementation 'org.assertj:assertj-core:3.24.2' } test { useJUnitPlatform() } jar { manifest { attributes( 'Main-Class': 'com.openfaas.entrypoint.App', 'Implementation-Title': project.name, 'Implementation-Version': project.version ) } } shadowJar { archiveBaseName.set('function') archiveClassifier.set('') archiveVersion.set('') mergeServiceFiles() // Relocate if necessary to avoid conflicts // relocate 'com.fasterxml.jackson', 'shadow.jackson' } build.dependsOn shadowJar templates/java17-gradle/function/build.gradle:
dependencies { implementation project(':') } // Custom task to copy the fat JAR task copyFunctionJar(type: Copy) { from shadowJar into "${project.rootDir}/function" rename { 'function.jar' } } build.dependsOn copyFunctionJar Step 3: Function Handler Base Classes
templates/java17-gradle/src/main/java/com/openfaas/function/core/AbstractFunction.java:
package com.openfaas.function.core; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.openfaas.model.IRequest; import com.openfaas.model.IResponse; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; import java.util.Map; @Slf4j public abstract class AbstractFunction { protected final ObjectMapper objectMapper; protected AbstractFunction() { this.objectMapper = new ObjectMapper(); this.objectMapper.findAndRegisterModules(); } public abstract IResponse handle(IRequest request); protected Map<String, String> createSuccessHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Powered-By", "OpenFaaS-Java"); return headers; } protected Map<String, String> createErrorHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Error", "true"); return headers; } protected String toJson(Object object) { try { return objectMapper.writeValueAsString(object); } catch (JsonProcessingException e) { log.error("Failed to serialize object to JSON", e); return "{\"error\": \"Serialization failed\"}"; } } protected <T> T fromJson(String json, Class<T> clazz) { try { return objectMapper.readValue(json, clazz); } catch (JsonProcessingException e) { log.error("Failed to deserialize JSON: {}", json, e); throw new FunctionException("JSON deserialization failed", e); } } protected IResponse createSuccessResponse(Object body) { IResponse response = new com.openfaas.model.Response(); response.setStatusCode(200); response.setBody(toJson(body)); response.setHeaders(createSuccessHeaders()); return response; } protected IResponse createErrorResponse(String message, int statusCode) { IResponse response = new com.openfaas.model.Response(); response.setStatusCode(statusCode); response.setBody(toJson(Map.of( "error", true, "message", message, "timestamp", System.currentTimeMillis() ))); response.setHeaders(createErrorHeaders()); return response; } protected IResponse createValidationError(String message) { return createErrorResponse(message, 400); } protected IResponse createInternalError(String message) { return createErrorResponse(message, 500); } protected void logRequest(IRequest request) { log.info("Processing request - Method: {}, Path: {}, Body Size: {}", request.getMethod(), request.getPath(), request.getBody() != null ? request.getBody().length() : 0); } public static class FunctionException extends RuntimeException { public FunctionException(String message) { super(message); } public FunctionException(String message, Throwable cause) { super(message, cause); } } } templates/java17-gradle/src/main/java/com/openfaas/function/core/AsyncFunction.java:
package com.openfaas.function.core; import com.openfaas.model.IRequest; import com.openfaas.model.IResponse; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public abstract class AsyncFunction extends AbstractFunction { private final ExecutorService executorService; protected AsyncFunction() { this.executorService = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors() ); } protected AsyncFunction(int threadPoolSize) { this.executorService = Executors.newFixedThreadPool(threadPoolSize); } public abstract CompletableFuture<IResponse> handleAsync(IRequest request); @Override public IResponse handle(IRequest request) { try { return handleAsync(request).get(); } catch (Exception e) { log.error("Async function execution failed", e); return createInternalError("Async execution failed: " + e.getMessage()); } } protected CompletableFuture<IResponse> processAsync(IRequest request) { return CompletableFuture.supplyAsync(() -> handle(request), executorService); } @Override protected void finalize() throws Throwable { executorService.shutdown(); super.finalize(); } } Step 4: Sample Functions
functions/user-service/src/main/java/com/example/function/UserFunction.java:
package com.example.function; import com.openfaas.function.core.AbstractFunction; import com.openfaas.model.IRequest; import com.openfaas.model.IResponse; import lombok.extern.slf4j.Slf4j; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @Slf4j public class UserFunction extends AbstractFunction { private final Map<String, User> userStore; public UserFunction() { this.userStore = new ConcurrentHashMap<>(); // Initialize with some sample data initializeSampleData(); } @Override public IResponse handle(IRequest request) { logRequest(request); try { String method = request.getMethod(); String path = request.getPath(); return switch (method) { case "GET" -> handleGetRequest(path); case "POST" -> handlePostRequest(request); case "PUT" -> handlePutRequest(path, request); case "DELETE" -> handleDeleteRequest(path); default -> createErrorResponse("Method not allowed", 405); }; } catch (Exception e) { log.error("User function error", e); return createInternalError("Processing failed: " + e.getMessage()); } } private IResponse handleGetRequest(String path) { if ("/users".equals(path)) { // Return all users return createSuccessResponse(Map.of( "users", userStore.values(), "count", userStore.size() )); } else if (path.startsWith("/users/")) { // Return specific user String userId = extractUserId(path); User user = userStore.get(userId); if (user != null) { return createSuccessResponse(user); } else { return createErrorResponse("User not found", 404); } } else { return createErrorResponse("Not found", 404); } } private IResponse handlePostRequest(IRequest request) { try { UserCreateRequest createRequest = fromJson(request.getBody(), UserCreateRequest.class); // Validate input if (createRequest.getName() == null || createRequest.getEmail() == null) { return createValidationError("Name and email are required"); } // Create user User user = User.builder() .id(UUID.randomUUID().toString()) .name(createRequest.getName()) .email(createRequest.getEmail()) .createdAt(System.currentTimeMillis()) .build(); userStore.put(user.getId(), user); log.info("Created user: {}", user.getId()); return createSuccessResponse(Map.of( "user", user, "message", "User created successfully" )); } catch (Exception e) { return createValidationError("Invalid request body"); } } private IResponse handlePutRequest(String path, IRequest request) { String userId = extractUserId(path); User existingUser = userStore.get(userId); if (existingUser == null) { return createErrorResponse("User not found", 404); } try { UserUpdateRequest updateRequest = fromJson(request.getBody(), UserUpdateRequest.class); // Update user if (updateRequest.getName() != null) { existingUser.setName(updateRequest.getName()); } if (updateRequest.getEmail() != null) { existingUser.setEmail(updateRequest.getEmail()); } existingUser.setUpdatedAt(System.currentTimeMillis()); userStore.put(userId, existingUser); log.info("Updated user: {}", userId); return createSuccessResponse(Map.of( "user", existingUser, "message", "User updated successfully" )); } catch (Exception e) { return createValidationError("Invalid request body"); } } private IResponse handleDeleteRequest(String path) { String userId = extractUserId(path); User removedUser = userStore.remove(userId); if (removedUser != null) { log.info("Deleted user: {}", userId); return createSuccessResponse(Map.of( "message", "User deleted successfully", "userId", userId )); } else { return createErrorResponse("User not found", 404); } } private String extractUserId(String path) { return path.substring("/users/".length()); } private void initializeSampleData() { User sampleUser = User.builder() .id("sample-1") .name("John Doe") .email("[email protected]") .createdAt(System.currentTimeMillis()) .build(); userStore.put(sampleUser.getId(), sampleUser); } // DTO classes @Data @Builder @NoArgsConstructor @AllArgsConstructor public static class User { private String id; private String name; private String email; private Long createdAt; private Long updatedAt; } @Data @NoArgsConstructor @AllArgsConstructor public static class UserCreateRequest { private String name; private String email; } @Data @NoArgsConstructor @AllArgsConstructor public static class UserUpdateRequest { private String name; private String email; } } functions/image-processor/src/main/java/com/example/function/ImageProcessorFunction.java:
package com.example.function; import com.openfaas.function.core.AbstractFunction; import com.openfaas.model.IRequest; import com.openfaas.model.IResponse; import lombok.extern.slf4j.Slf4j; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Base64; import java.util.Map; @Slf4j public class ImageProcessorFunction extends AbstractFunction { private static final int THUMBNAIL_WIDTH = 200; private static final int THUMBNAIL_HEIGHT = 200; @Override public IResponse handle(IRequest request) { logRequest(request); try { String operation = request.getQuery().get("operation"); if (operation == null) { return createValidationError("Operation parameter is required"); } return switch (operation) { case "thumbnail" -> createThumbnail(request); case "info" -> getImageInfo(request); case "convert" -> convertImage(request); default -> createErrorResponse("Unsupported operation: " + operation, 400); }; } catch (Exception e) { log.error("Image processing failed", e); return createInternalError("Image processing failed: " + e.getMessage()); } } private IResponse createThumbnail(IRequest request) { try { ImageRequest imageRequest = fromJson(request.getBody(), ImageRequest.class); byte[] imageData = Base64.getDecoder().decode(imageRequest.getImageData()); BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData)); if (originalImage == null) { return createValidationError("Invalid image data"); } // Create thumbnail BufferedImage thumbnail = new BufferedImage(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, originalImage.getType()); Graphics2D g = thumbnail.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(originalImage, 0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, null); g.dispose(); // Convert back to bytes ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(thumbnail, "jpg", baos); byte[] thumbnailData = baos.toByteArray(); String thumbnailBase64 = Base64.getEncoder().encodeToString(thumbnailData); log.info("Created thumbnail - Original: {}x{}, Thumbnail: {}x{}", originalImage.getWidth(), originalImage.getHeight(), THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); return createSuccessResponse(Map.of( "thumbnail", thumbnailBase64, "format", "jpg", "width", THUMBNAIL_WIDTH, "height", THUMBNAIL_HEIGHT, "size", thumbnailData.length )); } catch (Exception e) { return createValidationError("Invalid image data: " + e.getMessage()); } } private IResponse getImageInfo(IRequest request) { try { ImageRequest imageRequest = fromJson(request.getBody(), ImageRequest.class); byte[] imageData = Base64.getDecoder().decode(imageRequest.getImageData()); BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); if (image == null) { return createValidationError("Invalid image data"); } return createSuccessResponse(Map.of( "width", image.getWidth(), "height", image.getHeight(), "type", image.getType(), "size", imageData.length )); } catch (Exception e) { return createValidationError("Invalid image data: " + e.getMessage()); } } private IResponse convertImage(IRequest request) { try { ImageConvertRequest convertRequest = fromJson(request.getBody(), ImageConvertRequest.class); byte[] imageData = Base64.getDecoder().decode(convertRequest.getImageData()); BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); if (image == null) { return createValidationError("Invalid image data"); } String format = convertRequest.getFormat() != null ? convertRequest.getFormat() : "png"; ByteArrayOutputStream baos = new ByteArrayOutputStream(); boolean written = ImageIO.write(image, format, baos); if (!written) { return createErrorResponse("Unsupported format: " + format, 400); } byte[] convertedData = baos.toByteArray(); String convertedBase64 = Base64.getEncoder().encodeToString(convertedData); return createSuccessResponse(Map.of( "image", convertedBase64, "format", format, "size", convertedData.length )); } catch (Exception e) { return createValidationError("Conversion failed: " + e.getMessage()); } } // DTO classes @Data @NoArgsConstructor @AllArgsConstructor public static class ImageRequest { private String imageData; } @Data @NoArgsConstructor @AllArgsConstructor public static class ImageConvertRequest { private String imageData; private String format; } } Step 5: Spring Native Template
templates/java21-spring-native/template.yml:
language: java21-spring-native platform: x86_64 description: Spring Boot 3 with Native Image for OpenFaaS keywords: - java - spring-boot - native - graalvm - serverless readme: | # Spring Boot Native Template This template uses Spring Boot 3 with GraalVM Native Image for ultra-fast cold starts. Features: - Spring Boot 3.2+ - GraalVM Native Image - Sub-100ms cold starts - Minimal memory footprint - Spring WebFlux for reactive programming
templates/java21-spring-native/Dockerfile:
# Build stage FROM ghcr.io/graalvm/native-image:ol8-java-21 as builder WORKDIR /build # Install necessary build tools RUN microdnf update -y && \ microdnf install -y findutils zip && \ microdnf clean all # Copy build files COPY gradlew . COPY gradle gradle COPY build.gradle settings.gradle ./ COPY src src # Build native image RUN ./gradlew nativeCompile --no-daemon # Runtime stage FROM oraclelinux:8-slim RUN microdnf update -y && \ microdnf install -y gcompat && \ microdnf clean all # Create non-root user RUN groupadd -r app && useradd -r -g app app WORKDIR /app COPY --from=builder /build/build/native/nativeCompile/function function USER app EXPOSE 8080 ENV _HANDLER=/app/function CMD ["/app/function"]
templates/java21-spring-native/src/main/java/com/example/SpringNativeFunction.java:
package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; import static org.springframework.web.reactive.function.server.RouterFunctions.route; import static org.springframework.web.reactive.function.server.ServerResponse.ok; @SpringBootApplication public class SpringNativeFunction { public static void main(String[] args) { SpringApplication.run(SpringNativeFunction.class, args); } @RestController public static class FunctionController { @PostMapping("/**") public ResponseEntity<Object> handleRequest(@RequestBody(required = false) String body) { // Process request and return response return ResponseEntity.ok() .header("X-Powered-By", "Spring-Native-OpenFaaS") .body(Map.of( "message", "Hello from Spring Native!", "timestamp", System.currentTimeMillis(), "body", body != null ? body : "No body provided" )); } } // Alternative reactive approach @Bean public RouterFunction<ServerResponse> routes() { return route() .POST("/**", request -> ok().bodyValue(Map.of( "message", "Reactive response", "timestamp", System.currentTimeMillis() ))) .build(); } } Step 6: Quarkus Native Template
templates/quarkus-native/src/main/java/com/example/QuarkusFunction.java:
package com.example; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Map; @Path("/") public class QuarkusFunction { @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response handleRequest(Map<String, Object> input) { try { // Process the request String name = (String) input.getOrDefault("name", "World"); Map<String, Object> response = Map.of( "message", "Hello " + name + " from Quarkus Native!", "timestamp", System.currentTimeMillis(), "runtime", "Quarkus", "native", true ); return Response.ok(response) .header("X-Powered-By", "Quarkus-OpenFaaS") .build(); } catch (Exception e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } } templates/quarkus-native/Dockerfile:
FROM quay.io/quarkus/ubi-quarkus-native-image:22-java17 AS build WORKDIR /project COPY . . RUN ./mvnw package -Pnative -DskipTests FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6 WORKDIR /work/ COPY --from=build /project/target/*-runner /work/function RUN chmod 775 /work EXPOSE 8080 CMD ["./function", "-Dquarkus.http.host=0.0.0.0"]
Step 7: Build and Deployment Scripts
build-scripts/build-function.sh:
#!/bin/bash set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # Configuration FUNCTION_NAME="${1}" TEMPLATE="${2:-java17-gradle}" REGISTRY="${REGISTRY:-ghcr.io/your-username}" TAG="${TAG:-latest}" log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } validate_input() { if [ -z "$FUNCTION_NAME" ]; then log_error "Function name is required" echo "Usage: $0 <function-name> [template]" exit 1 fi if [ ! -d "functions/$FUNCTION_NAME" ]; then log_error "Function directory not found: functions/$FUNCTION_NAME" exit 1 fi if [ ! -d "templates/$TEMPLATE" ]; then log_error "Template not found: $TEMPLATE" exit 1 fi } build_function() { local function_dir="functions/$FUNCTION_NAME" local template_dir="templates/$TEMPLATE" local image_name="$REGISTRY/$FUNCTION_NAME:$TAG" log_info "Building function: $FUNCTION_NAME" log_info "Template: $TEMPLATE" log_info "Image: $image_name" # Copy template files to function directory cp -r "$template_dir"/* "$function_dir/" # Build the function cd "$function_dir" case $TEMPLATE in *gradle*) log_info "Building with Gradle..." ./gradlew build --no-daemon -x test ;; *maven*) log_info "Building with Maven..." ./mvnw package -DskipTests ;; *quarkus*) log_info "Building Quarkus native image..." ./mvnw package -Pnative -DskipTests ;; *) log_error "Unsupported build system for template: $TEMPLATE" exit 1 ;; esac # Build Docker image log_info "Building Docker image..." docker build -t "$image_name" . # Push to registry if [ "$PUSH" = "true" ]; then log_info "Pushing image to registry..." docker push "$image_name" fi log_info "Build completed: $image_name" } deploy_function() { local function_name="$1" local image_name="$REGISTRY/$function_name:$TAG" log_info "Deploying function: $function_name" # Deploy to OpenFaaS faas-cli deploy \ --name "$function_name" \ --image "$image_name" \ --env "JAVA_OPTS=-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" \ --label "com.openfaas.scale.min=1" \ --label "com.openfaas.scale.max=10" \ --label "com.openfaas.scale.factor=20" \ --annotation "prometheus.io.scrape=true" \ --annotation "prometheus.io.port=8080" log_info "Function deployed: $function_name" } # Main execution main() { validate_input build_function if [ "$DEPLOY" = "true" ]; then deploy_function "$FUNCTION_NAME" fi } # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in --push) PUSH="true" shift ;; --deploy) DEPLOY="true" shift ;; --tag) TAG="$2" shift 2 ;; --registry) REGISTRY="$2" shift 2 ;; *) shift ;; esac done main build-scripts/deploy-stack.yml:
version: 1.0 provider: name: openfaas gateway: http://127.0.0.1:8080 functions: user-service: lang: java17-gradle handler: ./functions/user-service image: ghcr.io/your-username/user-service:latest environment: JAVA_OPTS: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Xss256k" LOG_LEVEL: "INFO" labels: com.openfaas.scale.min: "1" com.openfaas.scale.max: "10" com.openfaas.scale.factor: "20" environment: "production" annotations: prometheus.io.scrape: "true" prometheus.io.port: "8080" limits: memory: 256M cpu: 200m requests: memory: 128M cpu: 100m image-processor: lang: java17-gradle handler: ./functions/image-processor image: ghcr.io/your-username/image-processor:latest environment: JAVA_OPTS: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Xmx128m" LOG_LEVEL: "INFO" labels: com.openfaas.scale.min: "1" com.openfaas.scale.max: "5" com.openfaas.scale.factor: "10" environment: "production" limits: memory: 512M cpu: 500m requests: memory: 256M cpu: 200m data-transformer: lang: java21-spring-native handler: ./functions/data-transformer image: ghcr.io/your-username/data-transformer:latest labels: com.openfaas.scale.min: "1" com.openfaas.scale.max: "20" environment: "production" annotations: prometheus.io.scrape: "true" limits: memory: 128M cpu: 100m requests: memory: 64M cpu: 50m
Step 8: Kubernetes Deployment
kubernetes/openfaas-namespace.yml:
apiVersion: v1 kind: Namespace metadata: name: openfaas labels: name: openfaas openfaas: "true"
kubernetes/java-function-deployment.yml:
apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: openfaas-fn labels: app: user-service function: "true" spec: replicas: 2 selector: matchLabels: app: user-service template: metadata: labels: app: user-service function: "true" annotations: prometheus.io.scrape: "true" prometheus.io.port: "8080" spec: containers: - name: user-service image: ghcr.io/your-username/user-service:latest ports: - containerPort: 8080 name: http env: - name: JAVA_OPTS value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Xss256k -Djava.security.egd=file:/dev/./urandom" - name: LOG_LEVEL value: "INFO" resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m" livenessProbe: httpGet: path: /_/health port: 8080 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /_/health port: 8080 initialDelaySeconds: 2 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: user-service namespace: openfaas-fn labels: app: user-service function: "true" spec: type: ClusterIP ports: - port: 8080 targetPort: 8080 protocol: TCP name: http selector: app: user-service
Step 9: Monitoring and Observability
templates/java17-gradle/src/main/java/com/openfaas/function/core/MetricsCollector.java:
package com.openfaas.function.core; import io.prometheus.client.Counter; import io.prometheus.client.Gauge; import io.prometheus.client.Histogram; import io.prometheus.client.exporter.HTTPServer; import java.io.IOException; public class MetricsCollector { private static final Counter requestCounter = Counter.build() .name("function_requests_total") .help("Total number of function requests") .labelNames("function", "method", "status") .register(); private static final Histogram requestDuration = Histogram.build() .name("function_request_duration_seconds") .help("Function request duration in seconds") .labelNames("function") .register(); private static final Gauge activeRequests = Gauge.build() .name("function_active_requests") .help("Number of active requests") .labelNames("function") .register(); private static HTTPServer metricsServer; public static void startMetricsServer(int port) { try { metricsServer = new HTTPServer(port); } catch (IOException e) { throw new RuntimeException("Failed to start metrics server", e); } } public static void recordRequest(String functionName, String method, String status, long durationMs) { requestCounter.labels(functionName, method, status).inc(); requestDuration.labels(functionName).observe(durationMs / 1000.0); } public static Histogram.Timer startRequestTimer(String functionName) { activeRequests.labels(functionName).inc(); return requestDuration.labels(functionName).startTimer(); } public static void endRequestTimer(String functionName, Histogram.Timer timer) { timer.observeDuration(); activeRequests.labels(functionName).dec(); } } Step 10: Usage Examples
Deploy a Function:
# Build and deploy user service ./build-scripts/build-function.sh user-service java17-gradle --push --deploy # Deploy with custom tag ./build-scripts/build-function.sh image-processor java17-gradle --tag v1.2.3 --push # Deploy using faas-cli faas-cli deploy -f build-scripts/deploy-stack.yml
Test Functions:
# Test user service curl -X POST http://localhost:8080/function/user-service \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "[email protected]"}' # Test image processor curl -X POST "http://localhost:8080/function/image-processor?operation=info" \ -H "Content-Type: application/json" \ -d '{"imageData": "base64-encoded-image"}' # Get function metrics curl http://localhost:8080/function/user-service/metrics Monitor Functions:
# List functions faas-cli list # Check function logs faas-cli logs user-service # Scale function faas-cli scale user-service --min=3 --max=10
Best Practices
1. Performance Optimization
- Use GraalVM Native Image for fastest cold starts
- Optimize JVM flags for container environments
- Implement connection pooling for external services
- Use efficient serialization libraries
2. Resource Management
- Set appropriate memory limits based on function requirements
- Configure CPU requests and limits
- Implement proper cleanup in functions
- Use async processing for I/O operations
3. Security
- Use non-root users in containers
- Scan images for vulnerabilities
- Implement input validation
- Use secure communication between functions
Benefits of OpenFaaS Java Templates
- Fast Cold Starts: Optimized JVM configurations and native images
- Resource Efficiency: Minimal memory footprint and CPU usage
- Developer Experience: Familiar Java tooling and frameworks
- Production Ready: Built-in monitoring, scaling, and health checks
- Ecosystem Integration: Seamless Kubernetes and Docker integration
Conclusion
OpenFaaS Java templates provide a powerful foundation for building serverless Java functions that are:
- High-performance with optimized cold starts
- Resource-efficient with proper JVM tuning
- Developer-friendly with familiar Java ecosystems
- Production-ready with comprehensive monitoring
- Scalable with built-in auto-scaling capabilities
By leveraging these templates and best practices, you can build robust, scalable serverless functions that take full advantage of Java's ecosystem while maintaining the benefits of serverless computing.