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.