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.