Serverless Java with Fission: Building and Deploying Functions

Article

Fission is a Kubernetes-native serverless framework that enables you to run functions without managing complex infrastructure. For Java developers, Fission provides a powerful way to deploy microservices, APIs, and event-driven functions with minimal overhead and maximum efficiency.


What is Fission?

Fission is an open-source serverless framework built on Kubernetes that allows you to write functions in various languages (including Java) and execute them in response to events. It handles auto-scaling, networking, and resource management while you focus on business logic.

Key Benefits for Java Teams:

  • No Infrastructure Management: Focus on code, not containers or pods
  • Fast Cold Starts: Pre-warmed pools and optimized environments
  • Event-Driven Architecture: Support for HTTP, message queues, timers, and more
  • Kubernetes Native: Leverages existing Kubernetes clusters and tools
  • Cost Effective: Pay only for execution time with automatic scaling to zero

Fission Architecture Overview

  • Router: Handles HTTP requests and routes to functions
  • Executor: Manages function instances (NewDeploy vs PoolManager)
  • Environment: Pre-built containers with language runtimes
  • Function: Your Java code with dependencies
  • Trigger: Event sources that invoke functions (HTTP, Message Queue, Timer)

Installation and Setup

Install Fission

Using Helm (Recommended):

# Add Fission chart repository
helm repo add fission https://fission.github.io/fission-charts/
helm repo update
# Install Fission in fission namespace
helm install fission fission/fission-all --version v1.18.0 --namespace fission --create-namespace

Using kubectl:

kubectl create namespace fission
kubectl -n fission apply -f https://github.com/fission/fission/releases/download/v1.18.0/fission-core-v1.18.0.yaml

Install Fission CLI

Mac:

curl -Lo fission https://github.com/fission/fission/releases/download/v1.18.0/fission-v1.18.0-darwin-amd64 
chmod +x fission && sudo mv fission /usr/local/bin/

Linux:

curl -Lo fission https://github.com/fission/fission/releases/download/v1.18.0/fission-v1.18.0-linux-amd64 
chmod +x fission && sudo mv fission /usr/local/bin/

Verify Installation:

fission version

Creating Java Functions

1. Simple HTTP Function

Basic Java Function:

package com.example.fission;
import io.fission.Function;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import java.util.Map;
import java.util.HashMap;
public class HelloWorldFunction implements Function {
@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
// Get query parameters
String name = request.getParams().getFirst("name");
if (name == null) {
name = "World";
}
// Create response
Map<String, String> response = new HashMap<>();
response.put("message", "Hello, " + name + "!");
response.put("timestamp", java.time.Instant.now().toString());
response.put("language", "Java");
return ResponseEntity.ok(response);
}
}

2. JSON Processing Function

package com.example.fission;
import io.fission.Function;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class JsonProcessorFunction implements Function {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
try {
// Parse request body
if (request.getBody() instanceof String) {
String body = (String) request.getBody();
Map<String, Object> data = mapper.readValue(body, Map.class);
// Process data
data.put("processed", true);
data.put("processedAt", java.time.Instant.now().toString());
data.put("processor", "java-function");
return ResponseEntity.ok(data);
} else {
return ResponseEntity.badRequest()
.body("{\"error\": \"Expected JSON body\"}");
}
} catch (Exception e) {
return ResponseEntity.status(500)
.body("{\"error\": \"Processing failed: " + e.getMessage() + "\"}");
}
}
}

3. Database-Aware Function

package com.example.fission;
import io.fission.Function;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.List;
import java.util.Map;
public class UserServiceFunction implements Function {
private JdbcTemplate jdbcTemplate;
public UserServiceFunction() {
// Initialize datasource from environment variables
String dbUrl = System.getenv("DB_URL");
String dbUser = System.getenv("DB_USERNAME");
String dbPassword = System.getenv("DB_PASSWORD");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl(dbUrl);
dataSource.setUsername(dbUser);
dataSource.setPassword(dbPassword);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
String method = request.getMethod().name();
String path = request.getUrl().getPath();
try {
switch (method) {
case "GET":
return handleGet(request);
case "POST":
return handlePost(request);
case "PUT":
return handlePut(request);
default:
return ResponseEntity.status(405).body("Method not allowed");
}
} catch (Exception e) {
return ResponseEntity.status(500)
.body("{\"error\": \"Database operation failed: " + e.getMessage() + "\"}");
}
}
private ResponseEntity<?> handleGet(RequestEntity<?> request) {
String path = request.getUrl().getPath();
if (path.endsWith("/users")) {
List<Map<String, Object>> users = jdbcTemplate.queryForList(
"SELECT id, name, email, created_at FROM users ORDER BY created_at DESC");
return ResponseEntity.ok(users);
} else {
return ResponseEntity.status(404).body("Not found");
}
}
private ResponseEntity<?> handlePost(RequestEntity<?> request) {
if (request.getBody() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> userData = (Map<String, Object>) request.getBody();
jdbcTemplate.update(
"INSERT INTO users (name, email) VALUES (?, ?)",
userData.get("name"), userData.get("email"));
return ResponseEntity.status(201).body("{\"status\": \"User created\"}");
}
return ResponseEntity.badRequest().body("{\"error\": \"Invalid user data\"}");
}
private ResponseEntity<?> handlePut(RequestEntity<?> request) {
// Implementation for update
return ResponseEntity.ok("{\"status\": \"Update endpoint\"}");
}
}

Build Configuration

Maven Configuration (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>fission-java-functions</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<fission.version>1.18.0</fission.version>
<spring.version>5.3.23</spring.version>
</properties>
<dependencies>
<!-- Fission Java Support -->
<dependency>
<groupId>io.fission</groupId>
<artifactId>java-function</artifactId>
<version>${fission.version}</version>
</dependency>
<!-- Spring Web for HTTP handling -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Jackson for JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<!-- Database Support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.4</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.fission.java.JavaMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Gradle Configuration (build.gradle)

plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
group = 'com.example'
version = '1.0.0'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'io.fission:java-function:1.18.0'
implementation 'org.springframework:spring-web:5.3.23'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
implementation 'org.springframework:spring-jdbc:5.3.23'
implementation 'org.postgresql:postgresql:42.5.4'
implementation 'org.slf4j:slf4j-simple:2.0.6'
}
shadowJar {
mergeServiceFiles()
manifest {
attributes 'Main-Class': 'io.fission.java.JavaMain'
}
}
assemble.dependsOn shadowJar

Deploying Java Functions

1. Create Java Environment

# Create a Java environment with JRE 11
fission environment create --name java11 \
--image fission/jvm-env:1.18.0 \
--version 3 \
--poolsize 3 \
--builder fission/jvm-builder:1.18.0 \
--mincpu 100 --maxcpu 500 \
--minmemory 128 --maxmemory 512

2. Deploy Simple Function

# Build the project first
mvn clean package
# Create the function
fission function create --name helloworld \
--env java11 \
--code target/fission-java-functions-1.0.0.jar \
--entrypoint "com.example.fission.HelloWorldFunction" \
--executortype newdeploy \
--minscale 1 --maxscale 5

3. Create HTTP Trigger

# Create route for the function
fission route create --name helloworld-route \
--function helloworld \
--url /hello \
--method GET

4. Test the Function

# Invoke the function via HTTP
curl http://$FISSION_ROUTER/hello?name=JavaDeveloper
# Expected response:
# {"message":"Hello, JavaDeveloper!","timestamp":"2023-11-15T10:30:00Z","language":"Java"}

Advanced Function Patterns

1. Function with Secrets

# Create secret for database credentials
kubectl create secret generic db-secret \
--namespace fission-function \
--from-literal=db-url=jdbc:postgresql://postgresql:5432/mydb \
--from-literal=db-username=admin \
--from-literal=db-password=secret123
# Create function with secret
fission function create --name user-service \
--env java11 \
--code target/functions.jar \
--entrypoint "com.example.fission.UserServiceFunction" \
--secret db-secret

2. Timer-Based Function (Cron Job)

package com.example.fission;
import io.fission.Function;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
public class ScheduledCleanupFunction implements Function {
@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
// This function runs on a schedule
performCleanup();
logCleanupActivity();
return ResponseEntity.ok("{\"status\": \"Cleanup completed\"}");
}
private void performCleanup() {
// Clean up temporary files, expired sessions, etc.
System.out.println("Performing scheduled cleanup at " + 
java.time.Instant.now());
}
private void logCleanupActivity() {
// Log to centralized logging system
System.out.println("Cleanup activity logged");
}
}

Create Timer Trigger:

# Create the function first
fission function create --name scheduled-cleanup \
--env java11 \
--code target/functions.jar \
--entrypoint "com.example.fission.ScheduledCleanupFunction"
# Create timer trigger that runs every hour
fission timer create --name hourly-cleanup \
--function scheduled-cleanup \
--cron "0 * * * *"

3. Message Queue Function

package com.example.fission;
import io.fission.Function;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MessageProcessorFunction implements Function {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
try {
String messageBody = (String) request.getBody();
Message message = mapper.readValue(messageBody, Message.class);
// Process the message
processMessage(message);
return ResponseEntity.ok("{\"status\": \"Message processed\"}");
} catch (Exception e) {
// In message processing, you might want to handle errors differently
System.err.println("Failed to process message: " + e.getMessage());
return ResponseEntity.status(500).body("{\"error\": \"Processing failed\"}");
}
}
private void processMessage(Message message) {
// Business logic for message processing
System.out.println("Processing message: " + message.getId());
// Example: Send email, update database, call external API
switch (message.getType()) {
case "USER_REGISTERED":
sendWelcomeEmail(message.getUserId());
break;
case "ORDER_PLACED":
updateInventory(message.getOrderId());
break;
default:
System.out.println("Unknown message type: " + message.getType());
}
}
private void sendWelcomeEmail(String userId) {
// Implementation for sending email
System.out.println("Sending welcome email to user: " + userId);
}
private void updateInventory(String orderId) {
// Implementation for inventory update
System.out.println("Updating inventory for order: " + orderId);
}
// Inner class for message structure
public static class Message {
private String id;
private String type;
private String userId;
private String orderId;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
}
}

Create Message Queue Trigger:

# Create function
fission function create --name message-processor \
--env java11 \
--code target/functions.jar \
--entrypoint "com.example.fission.MessageProcessorFunction"
# Create Kafka trigger (if Kafka is configured)
fission mqt create --name kafka-trigger \
--function message-processor \
--mqtype kafka \
--topic user-events \
--resptopic responses

Monitoring and Debugging

1. Function Logs

# View function logs
fission function logs --name helloworld
# Follow logs in real-time
fission function logs --name helloworld --follow
# View logs with specific time range
fission function logs --name helloworld --since 1h

2. Function Metrics

# Get function information
fission function get --name helloworld
# List all functions
fission function list
# Get function metrics
fission function metrics --name helloworld

3. Testing and Debugging

# Test function directly
fission function test --name helloworld
# Test with specific input
echo '{"name": "TestUser"}' | fission function test --name helloworld
# Update function code
fission function update --name helloworld --code target/new-version.jar

Best Practices for Java Functions

1. Keep Functions Stateless

public class StatelessFunction implements Function {
@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
// Avoid instance variables that maintain state between invocations
// Use external storage (database, Redis) for state persistence
return ResponseEntity.ok("Stateless response");
}
}

2. Optimize Cold Starts

# Use pre-warmed pool with optimal size
fission environment create --name java11-optimized \
--image fission/jvm-env:1.18.0 \
--poolsize 5 \
--minscale 2

3. Proper Resource Allocation

# Set appropriate resource limits
fission function create --name resource-aware \
--env java11 \
--code target/app.jar \
--entrypoint "com.example.Function" \
--minmemory 256 --maxmemory 512 \
--mincpu 200 --maxcpu 1000

4. Error Handling and Retries

@Override
public ResponseEntity<?> call(RequestEntity<?> request, Context context) {
try {
return processRequest(request);
} catch (TemporaryFailureException e) {
// Return 5xx for retryable errors
return ResponseEntity.status(503)
.body("{\"error\": \"Service temporarily unavailable\"}");
} catch (PermanentFailureException e) {
// Return 4xx for permanent failures
return ResponseEntity.status(400)
.body("{\"error\": \"Invalid request: " + e.getMessage() + "\"}");
}
}

CI/CD Pipeline Example

GitHub Actions Workflow

name: Deploy Fission Function
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build with Maven
run: mvn -B package -DskipTests
- name: Install Fission CLI
run: |
curl -Lo fission https://github.com/fission/fission/releases/download/v1.18.0/fission-v1.18.0-linux-amd64
chmod +x fission
sudo mv fission /usr/local/bin/
- name: Configure Kubernetes
run: |
echo "${{ secrets.KUBECONFIG }}" > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy Function
run: |
fission function update --name helloworld --code target/fission-java-functions-1.0.0.jar
# Or create if it doesn't exist
# fission function create --name helloworld --env java11 --code target/fission-java-functions-1.0.0.jar --entrypoint "com.example.fission.HelloWorldFunction"

Conclusion

Fission provides Java developers with a powerful platform for building serverless functions that can scale automatically and integrate seamlessly with Kubernetes ecosystems. Key advantages include:

  • Rapid Development: Focus on business logic without infrastructure concerns
  • Java Ecosystem: Leverage existing Java libraries and frameworks
  • Event-Driven Architecture: Support for multiple trigger types
  • Cost Efficiency: Scale to zero when not in use
  • Kubernetes Native: Integrates with existing Kubernetes tooling

For Java teams looking to adopt serverless patterns without abandoning their language expertise, Fission offers an ideal balance of productivity, performance, and platform integration.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper