Fission is a fast serverless framework for Kubernetes that lets you write functions in any language and package them as containers. This guide covers Java function development with Fission.
Core Concepts
What is Fission?
- Serverless framework for Kubernetes
- Event-driven function execution
- Cold start optimization via pool managers
- Supports multiple languages including Java
- Built-in autoscaling and metrics
Key Components:
- Functions: Your Java code that handles requests
- Environments: Runtime environments (Java, Node.js, Python, etc.)
- Triggers: HTTP, Message Queue, Time-based triggers
- Packages: Packaged function code and dependencies
Dependencies and Setup
Maven Dependencies
<properties>
<fission.java.version>1.0.0</fission.java.version>
<spring-boot.version>3.1.0</spring-boot.version>
<jackson.version>2.15.2</jackson.version>
<fabric8-client.version>6.7.2</fabric8-client.version>
</properties>
<dependencies>
<!-- Fission Java Core -->
<dependency>
<groupId>io.fission</groupId>
<artifactId>fission-java-core</artifactId>
<version>${fission.java.version}</version>
</dependency>
<!-- Spring Boot (Optional, for more complex functions) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<!-- Fission Client (for function management) -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${fabric8-client.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</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.FissionMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Fission Configuration
# fission-config.yaml fission: router: http://router.fission environment: java namespace: fission-function timeout: 60 memory: 128Mi cpu: 100m logging: level: INFO
Core Fission Function Implementation
1. Basic Fission Function Interface
package io.fission;
import java.util.Map;
/**
* Core interface for Fission Java functions
*/
@FunctionalInterface
public interface FissionFunction {
/**
* Process an incoming request and return a response
*
* @param context Function context containing request data
* @return Response object
*/
Response call(Context context);
/**
* Default health check endpoint
*/
default Response health(Context context) {
return Response.ok()
.body("{\"status\": \"healthy\"}")
.header("Content-Type", "application/json")
.build();
}
/**
* Default initialization method
*/
default void init() {
// Can be overridden for initialization logic
}
/**
* Default cleanup method
*/
default void destroy() {
// Can be overridden for cleanup logic
}
}
2. Context and Response Models
package io.fission;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Function context containing request information
*/
public class Context {
private final String body;
private final Map<String, String> headers;
private final Map<String, String> query;
private final String method;
private final String url;
private final Map<String, Object> attributes;
public Context(String body, Map<String, String> headers,
Map<String, String> query, String method, String url) {
this.body = body;
this.headers = headers != null ? new HashMap<>(headers) : new HashMap<>();
this.query = query != null ? new HashMap<>(query) : new HashMap<>();
this.method = method;
this.url = url;
this.attributes = new HashMap<>();
}
// Getters
public String getBody() { return body; }
public Map<String, String> getHeaders() { return Collections.unmodifiableMap(headers); }
public Map<String, String> getQuery() { return Collections.unmodifiableMap(query); }
public String getMethod() { return method; }
public String getUrl() { return url; }
// Attribute management
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
public <T> T getAttribute(String key, Class<T> type) {
Object value = attributes.get(key);
return type.isInstance(value) ? type.cast(value) : null;
}
// Helper methods
public String getHeader(String name) {
return headers.get(name);
}
public String getQueryParam(String name) {
return query.get(name);
}
public boolean hasBody() {
return body != null && !body.isEmpty();
}
public static class Builder {
private String body;
private Map<String, String> headers;
private Map<String, String> query;
private String method;
private String url;
public Builder body(String body) {
this.body = body;
return this;
}
public Builder headers(Map<String, String> headers) {
this.headers = headers;
return this;
}
public Builder query(Map<String, String> query) {
this.query = query;
return this;
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder url(String url) {
this.url = url;
return this;
}
public Context build() {
return new Context(body, headers, query, method, url);
}
}
}
package io.fission;
import java.util.HashMap;
import java.util.Map;
/**
* Function response object
*/
public class Response {
private final int status;
private final String body;
private final Map<String, String> headers;
private Response(int status, String body, Map<String, String> headers) {
this.status = status;
this.body = body;
this.headers = headers != null ? new HashMap<>(headers) : new HashMap<>();
}
// Getters
public int getStatus() { return status; }
public String getBody() { return body; }
public Map<String, String> getHeaders() { return new HashMap<>(headers); }
public static ResponseBuilder ok() {
return new ResponseBuilder(200);
}
public static ResponseBuilder created() {
return new ResponseBuilder(201);
}
public static ResponseBuilder accepted() {
return new ResponseBuilder(202);
}
public static ResponseBuilder noContent() {
return new ResponseBuilder(204);
}
public static ResponseBuilder badRequest() {
return new ResponseBuilder(400);
}
public static ResponseBuilder unauthorized() {
return new ResponseBuilder(401);
}
public static ResponseBuilder forbidden() {
return new ResponseBuilder(403);
}
public static ResponseBuilder notFound() {
return new ResponseBuilder(404);
}
public static ResponseBuilder error() {
return new ResponseBuilder(500);
}
public static class ResponseBuilder {
private int status;
private String body;
private Map<String, String> headers = new HashMap<>();
public ResponseBuilder(int status) {
this.status = status;
}
public ResponseBuilder status(int status) {
this.status = status;
return this;
}
public ResponseBuilder body(String body) {
this.body = body;
return this;
}
public ResponseBuilder body(Object body) {
if (body instanceof String) {
this.body = (String) body;
} else {
// JSON serialization would go here
this.body = body.toString();
}
return this;
}
public ResponseBuilder header(String name, String value) {
this.headers.put(name, value);
return this;
}
public ResponseBuilder contentType(String contentType) {
this.headers.put("Content-Type", contentType);
return this;
}
public ResponseBuilder json() {
return contentType("application/json");
}
public ResponseBuilder text() {
return contentType("text/plain");
}
public ResponseBuilder html() {
return contentType("text/html");
}
public Response build() {
return new Response(status, body, headers);
}
}
}
3. Fission Main Entry Point
package io.fission;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
/**
* Main entry point for Fission Java functions
*/
public class FissionMain {
private static final Logger logger = LoggerFactory.getLogger(FissionMain.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
private static FissionFunction function;
private static HttpServer server;
public static void main(String[] args) throws Exception {
logger.info("Starting Fission Java Function");
// Load function class
String functionClass = System.getenv("FISSION_FUNCTION_CLASS");
if (functionClass == null) {
functionClass = "com.example.MyFunction"; // Default class
}
logger.info("Loading function class: {}", functionClass);
function = loadFunction(functionClass);
// Initialize function
function.init();
// Start HTTP server
int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8888"));
startServer(port);
logger.info("Fission Java Function started on port: {}", port);
// Add shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("Shutting down Fission Java Function");
if (function != null) {
function.destroy();
}
if (server != null) {
server.stop(0);
}
}));
}
private static FissionFunction loadFunction(String className) throws Exception {
Class<?> clazz = Class.forName(className);
return (FissionFunction) clazz.getDeclaredConstructor().newInstance();
}
private static void startServer(int port) throws Exception {
server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new FissionHandler());
server.createContext("/health", new HealthHandler());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
}
static class FissionHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
// Parse request
String method = exchange.getRequestMethod();
String body = readRequestBody(exchange);
Map<String, String> headers = extractHeaders(exchange);
Map<String, String> query = extractQueryParams(exchange);
// Create context
Context context = new Context.Builder()
.body(body)
.headers(headers)
.query(query)
.method(method)
.url(exchange.getRequestURI().toString())
.build();
// Execute function
Response response = function.call(context);
// Send response
exchange.getResponseHeaders().putAll(response.getHeaders());
exchange.sendResponseHeaders(response.getStatus(),
response.getBody() != null ? response.getBody().getBytes().length : 0);
if (response.getBody() != null) {
try (OutputStream os = exchange.getResponseBody()) {
os.write(response.getBody().getBytes());
}
}
} catch (Exception e) {
logger.error("Error handling request", e);
try {
exchange.sendResponseHeaders(500, 0);
} catch (Exception ex) {
logger.error("Error sending error response", ex);
}
} finally {
exchange.close();
}
}
private String readRequestBody(HttpExchange exchange) {
try (InputStream is = exchange.getRequestBody();
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
StringBuilder body = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
return body.toString();
} catch (Exception e) {
logger.warn("Failed to read request body", e);
return "";
}
}
private Map<String, String> extractHeaders(HttpExchange exchange) {
Map<String, String> headers = new HashMap<>();
exchange.getRequestHeaders().forEach((key, values) -> {
if (!values.isEmpty()) {
headers.put(key, values.get(0));
}
});
return headers;
}
private Map<String, String> extractQueryParams(HttpExchange exchange) {
Map<String, String> query = new HashMap<>();
String queryString = exchange.getRequestURI().getQuery();
if (queryString != null) {
for (String param : queryString.split("&")) {
String[] pair = param.split("=");
if (pair.length > 1) {
query.put(pair[0], pair[1]);
}
}
}
return query;
}
}
static class HealthHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
Context context = new Context.Builder()
.method("GET")
.url("/health")
.build();
Response response = function.health(context);
exchange.getResponseHeaders().putAll(response.getHeaders());
exchange.sendResponseHeaders(response.getStatus(),
response.getBody() != null ? response.getBody().getBytes().length : 0);
if (response.getBody() != null) {
try (OutputStream os = exchange.getResponseBody()) {
os.write(response.getBody().getBytes());
}
}
} catch (Exception e) {
logger.error("Error in health check", e);
try {
exchange.sendResponseHeaders(500, 0);
} catch (Exception ex) {
logger.error("Error sending health error response", ex);
}
} finally {
exchange.close();
}
}
}
}
Example Fission Functions
1. Simple HTTP Function
package com.example.functions;
import io.fission.Context;
import io.fission.FissionFunction;
import io.fission.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Simple greeting function
*/
public class GreetingFunction implements FissionFunction {
private static final Logger logger = LoggerFactory.getLogger(GreetingFunction.class);
@Override
public Response call(Context context) {
logger.info("Processing greeting request");
String name = context.getQueryParam("name");
if (name == null) {
name = "World";
}
String greeting = String.format("Hello, %s!", name);
return Response.ok()
.body(String.format("{\"greeting\": \"%s\"}", greeting))
.json()
.build();
}
@Override
public void init() {
logger.info("Initializing GreetingFunction");
// Initialize resources if needed
}
@Override
public void destroy() {
logger.info("Cleaning up GreetingFunction");
// Cleanup resources if needed
}
}
2. JSON Processing Function
package com.example.functions;
import io.fission.Context;
import io.fission.FissionFunction;
import io.fission.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* JSON processing function for user data
*/
public class UserProcessorFunction implements FissionFunction {
private static final Logger logger = LoggerFactory.getLogger(UserProcessorFunction.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Response call(Context context) {
try {
logger.info("Processing user data");
if (!context.hasBody()) {
return Response.badRequest()
.body("{\"error\": \"Request body required\"}")
.json()
.build();
}
// Parse JSON body
Map<String, Object> userData = objectMapper.readValue(
context.getBody(), new TypeReference<Map<String, Object>>() {});
// Process user data
String email = (String) userData.get("email");
String name = (String) userData.get("name");
if (email == null || name == null) {
return Response.badRequest()
.body("{\"error\": \"Email and name are required\"}")
.json()
.build();
}
// Generate user ID
String userId = generateUserId(email);
// Create response
Map<String, Object> response = new HashMap<>();
response.put("userId", userId);
response.put("email", email);
response.put("name", name);
response.put("processed", true);
response.put("timestamp", System.currentTimeMillis());
String responseBody = objectMapper.writeValueAsString(response);
return Response.ok()
.body(responseBody)
.json()
.build();
} catch (Exception e) {
logger.error("Error processing user data", e);
return Response.error()
.body("{\"error\": \"Failed to process user data\"}")
.json()
.build();
}
}
private String generateUserId(String email) {
// Simple hash-based user ID generation
int hashCode = email.hashCode();
return "user_" + Math.abs(hashCode);
}
@Override
public void init() {
logger.info("Initializing UserProcessorFunction");
}
@Override
public void destroy() {
logger.info("Cleaning up UserProcessorFunction");
}
}
3. Database Integration Function
package com.example.functions;
import io.fission.Context;
import io.fission.FissionFunction;
import io.fission.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Database query function with connection pooling
*/
public class DatabaseQueryFunction implements FissionFunction {
private static final Logger logger = LoggerFactory.getLogger(DatabaseQueryFunction.class);
private Connection connection;
@Override
public Response call(Context context) {
try {
logger.info("Executing database query");
String query = context.getQueryParam("query");
if (query == null) {
return Response.badRequest()
.body("{\"error\": \"Query parameter required\"}")
.json()
.build();
}
// Execute query
List<Map<String, Object>> results = executeQuery(query);
// Build response
Map<String, Object> response = new HashMap<>();
response.put("results", results);
response.put("count", results.size());
response.put("query", query);
return Response.ok()
.body(convertToJson(response))
.json()
.build();
} catch (SQLException e) {
logger.error("Database query failed", e);
return Response.error()
.body("{\"error\": \"Database query failed: " + e.getMessage() + "\"}")
.json()
.build();
} catch (Exception e) {
logger.error("Unexpected error", e);
return Response.error()
.body("{\"error\": \"Unexpected error: " + e.getMessage() + "\"}")
.json()
.build();
}
}
@Override
public void init() {
logger.info("Initializing DatabaseQueryFunction");
try {
// Get database configuration from environment
String dbUrl = System.getenv("DATABASE_URL");
String dbUser = System.getenv("DATABASE_USER");
String dbPassword = System.getenv("DATABASE_PASSWORD");
if (dbUrl == null) {
logger.warn("DATABASE_URL not set, using in-memory database");
// Use in-memory H2 for testing
dbUrl = "jdbc:h2:mem:testdb";
dbUser = "sa";
dbPassword = "";
}
// Create database connection
connection = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
logger.info("Database connection established");
// Initialize schema if needed
initializeSchema();
} catch (SQLException e) {
logger.error("Failed to initialize database connection", e);
throw new RuntimeException("Database initialization failed", e);
}
}
@Override
public void destroy() {
logger.info("Cleaning up DatabaseQueryFunction");
if (connection != null) {
try {
connection.close();
logger.info("Database connection closed");
} catch (SQLException e) {
logger.error("Error closing database connection", e);
}
}
}
private void initializeSchema() throws SQLException {
String createTableSQL = """
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""";
try (Statement stmt = connection.createStatement()) {
stmt.execute(createTableSQL);
logger.info("Database schema initialized");
}
}
private List<Map<String, Object>> executeQuery(String query) throws SQLException {
List<Map<String, Object>> results = new ArrayList<>();
try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new HashMap<>();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = rs.getObject(i);
row.put(columnName, value);
}
results.add(row);
}
}
return results;
}
private String convertToJson(Map<String, Object> data) {
// Simple JSON conversion (in production, use Jackson)
StringBuilder json = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (!first) {
json.append(",");
}
json.append("\"").append(entry.getKey()).append("\":");
if (entry.getValue() instanceof String) {
json.append("\"").append(entry.getValue()).append("\"");
} else if (entry.getValue() instanceof List) {
json.append(convertListToJson((List<?>) entry.getValue()));
} else {
json.append(entry.getValue());
}
first = false;
}
json.append("}");
return json.toString();
}
private String convertListToJson(List<?> list) {
StringBuilder json = new StringBuilder("[");
boolean first = true;
for (Object item : list) {
if (!first) {
json.append(",");
}
if (item instanceof Map) {
json.append(convertToJson((Map<String, Object>) item));
} else if (item instanceof String) {
json.append("\"").append(item).append("\"");
} else {
json.append(item);
}
first = false;
}
json.append("]");
return json.toString();
}
}
4. File Processing Function
package com.example.functions;
import io.fission.Context;
import io.fission.FissionFunction;
import io.fission.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* File processing function for base64 encoded files
*/
public class FileProcessorFunction implements FissionFunction {
private static final Logger logger = LoggerFactory.getLogger(FileProcessorFunction.class);
@Override
public Response call(Context context) {
try {
logger.info("Processing file upload");
if (!context.hasBody()) {
return Response.badRequest()
.body("{\"error\": \"File data required\"}")
.json()
.build();
}
// Parse request
String contentType = context.getHeader("Content-Type");
String fileName = context.getHeader("X-File-Name");
String fileData = context.getBody();
// Validate input
if (fileName == null) {
return Response.badRequest()
.body("{\"error\": \"X-File-Name header required\"}")
.json()
.build();
}
// Process file (base64 decode)
byte[] fileBytes;
try {
fileBytes = Base64.getDecoder().decode(fileData);
} catch (IllegalArgumentException e) {
return Response.badRequest()
.body("{\"error\": \"Invalid base64 data\"}")
.json()
.build();
}
// Analyze file
FileAnalysis analysis = analyzeFile(fileName, fileBytes, contentType);
// Build response
Map<String, Object> response = new HashMap<>();
response.put("fileName", fileName);
response.put("fileSize", analysis.getFileSize());
response.put("fileType", analysis.getFileType());
response.put("contentType", contentType);
response.put("analysis", analysis.getAnalysisResults());
response.put("processed", true);
response.put("timestamp", System.currentTimeMillis());
return Response.ok()
.body(convertToJson(response))
.json()
.build();
} catch (Exception e) {
logger.error("Error processing file", e);
return Response.error()
.body("{\"error\": \"File processing failed: " + e.getMessage() + "\"}")
.json()
.build();
}
}
private FileAnalysis analyzeFile(String fileName, byte[] fileBytes, String contentType) {
FileAnalysis analysis = new FileAnalysis();
analysis.setFileSize(fileBytes.length);
analysis.setFileType(detectFileType(fileName, fileBytes, contentType));
Map<String, Object> analysisResults = new HashMap<>();
analysisResults.put("sizeBytes", fileBytes.length);
analysisResults.put("sizeReadable", formatFileSize(fileBytes.length));
analysisResults.put("isText", isTextFile(fileBytes));
analysisResults.put("isImage", isImageFile(contentType));
if (isTextFile(fileBytes)) {
analysisResults.put("lineCount", countLines(fileBytes));
analysisResults.put("characterCount", fileBytes.length);
}
analysis.setAnalysisResults(analysisResults);
return analysis;
}
private String detectFileType(String fileName, byte[] fileBytes, String contentType) {
if (contentType != null) {
return contentType;
}
// Simple file type detection based on extension
if (fileName.toLowerCase().endsWith(".txt")) {
return "text/plain";
} else if (fileName.toLowerCase().endsWith(".json")) {
return "application/json";
} else if (fileName.toLowerCase().endsWith(".xml")) {
return "application/xml";
} else if (fileName.toLowerCase().endsWith(".pdf")) {
return "application/pdf";
} else {
return "application/octet-stream";
}
}
private boolean isTextFile(byte[] fileBytes) {
// Simple text file detection
for (byte b : fileBytes) {
if (b < 9 || (b > 13 && b < 32) && b != 27) {
return false;
}
}
return true;
}
private boolean isImageFile(String contentType) {
return contentType != null && contentType.startsWith("image/");
}
private int countLines(byte[] fileBytes) {
String content = new String(fileBytes, StandardCharsets.UTF_8);
return content.split("\r\n|\r|\n").length;
}
private String formatFileSize(long bytes) {
if (bytes < 1024) {
return bytes + " B";
} else if (bytes < 1024 * 1024) {
return String.format("%.1f KB", bytes / 1024.0);
} else {
return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
}
}
private String convertToJson(Map<String, Object> data) {
// Simple JSON conversion
StringBuilder json = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (!first) {
json.append(",");
}
json.append("\"").append(entry.getKey()).append("\":");
if (entry.getValue() instanceof String) {
json.append("\"").append(entry.getValue()).append("\"");
} else if (entry.getValue() instanceof Map) {
json.append(convertToJson((Map<String, Object>) entry.getValue()));
} else {
json.append(entry.getValue());
}
first = false;
}
json.append("}");
return json.toString();
}
static class FileAnalysis {
private long fileSize;
private String fileType;
private Map<String, Object> analysisResults;
// Getters and setters
public long getFileSize() { return fileSize; }
public void setFileSize(long fileSize) { this.fileSize = fileSize; }
public String getFileType() { return fileType; }
public void setFileType(String fileType) { this.fileType = fileType; }
public Map<String, Object> getAnalysisResults() { return analysisResults; }
public void setAnalysisResults(Map<String, Object> analysisResults) {
this.analysisResults = analysisResults;
}
}
}
5. Advanced: Spring Boot Integration Function
package com.example.functions;
import io.fission.Context;
import io.fission.FissionFunction;
import io.fission.Response;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Advanced function with Spring Boot integration
*/
public class SpringBootFunction implements FissionFunction {
private static final Logger logger = LoggerFactory.getLogger(SpringBootFunction.class);
private static ConfigurableApplicationContext springContext;
private static UserService userService;
@Override
public Response call(Context context) {
try {
logger.info("Processing request with Spring Boot");
// Ensure Spring context is initialized
initializeSpringContext();
// Extract user ID from path or query
String userId = extractUserId(context);
if (userId == null) {
return Response.badRequest()
.body("{\"error\": \"User ID required\"}")
.json()
.build();
}
// Process based on HTTP method
String method = context.getMethod();
switch (method) {
case "GET":
return getUser(userId);
case "POST":
case "PUT":
return updateUser(userId, context.getBody());
case "DELETE":
return deleteUser(userId);
default:
return Response.badRequest()
.body("{\"error\": \"Unsupported method: " + method + "\"}")
.json()
.build();
}
} catch (Exception e) {
logger.error("Error in Spring Boot function", e);
return Response.error()
.body("{\"error\": \"Processing failed: " + e.getMessage() + "\"}")
.json()
.build();
}
}
@Override
public void init() {
logger.info("Initializing SpringBootFunction");
initializeSpringContext();
}
@Override
public void destroy() {
logger.info("Cleaning up SpringBootFunction");
if (springContext != null) {
springContext.close();
springContext = null;
userService = null;
}
}
private synchronized void initializeSpringContext() {
if (springContext == null) {
logger.info("Starting Spring Boot application");
springContext = SpringApplication.run(FunctionApplication.class);
userService = springContext.getBean(UserService.class);
}
}
private String extractUserId(Context context) {
// Extract from path: /users/{userId}
String path = context.getUrl();
if (path.startsWith("/users/")) {
return path.substring(7); // Remove "/users/" prefix
}
// Extract from query parameter
return context.getQueryParam("userId");
}
private Response getUser(String userId) {
try {
User user = userService.getUser(userId);
if (user == null) {
return Response.notFound()
.body("{\"error\": \"User not found\"}")
.json()
.build();
}
return Response.ok()
.body(convertToJson(user))
.json()
.build();
} catch (Exception e) {
logger.error("Error getting user", e);
return Response.error()
.body("{\"error\": \"Failed to get user\"}")
.json()
.build();
}
}
private Response updateUser(String userId, String body) {
try {
User user = parseUser(body);
user.setId(userId);
User updatedUser = userService.saveUser(user);
return Response.ok()
.body(convertToJson(updatedUser))
.json()
.build();
} catch (Exception e) {
logger.error("Error updating user", e);
return Response.error()
.body("{\"error\": \"Failed to update user\"}")
.json()
.build();
}
}
private Response deleteUser(String userId) {
try {
userService.deleteUser(userId);
return Response.noContent()
.build();
} catch (Exception e) {
logger.error("Error deleting user", e);
return Response.error()
.body("{\"error\": \"Failed to delete user\"}")
.json()
.build();
}
}
private User parseUser(String body) {
// Simple JSON parsing (use Jackson in production)
String[] parts = body.replace("{", "").replace("}", "").replace("\"", "").split(",");
User user = new User();
for (String part : parts) {
String[] keyValue = part.split(":");
if (keyValue.length == 2) {
String key = keyValue[0].trim();
String value = keyValue[1].trim();
switch (key) {
case "name":
user.setName(value);
break;
case "email":
user.setEmail(value);
break;
}
}
}
return user;
}
private String convertToJson(User user) {
return String.format(
"{\"id\": \"%s\", \"name\": \"%s\", \"email\": \"%s\"}",
user.getId(), user.getName(), user.getEmail()
);
}
@SpringBootApplication
public static class FunctionApplication {
public static void main(String[] args) {
SpringApplication.run(FunctionApplication.class, args);
}
}
@RestController
public static class UserController {
// REST endpoints would be defined here for traditional Spring Boot usage
}
@Service
public static class UserService {
private final Map<String, User> userStore = new ConcurrentHashMap<>();
public User getUser(String userId) {
return userStore.get(userId);
}
public User saveUser(User user) {
userStore.put(user.getId(), user);
return user;
}
public void deleteUser(String userId) {
userStore.remove(userId);
}
}
public static class User {
private String id;
private String name;
private String email;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
}
Fission Deployment Configuration
1. Environment Configuration
# java-env.yaml apiVersion: fission.io/v1 kind: Environment metadata: name: java-env namespace: fission spec: runtime: image: fission/jvm-env imagePullPolicy: IfNotPresent builder: image: fission/jvm-builder imagePullPolicy: IfNotPresent version: 3 poolsize: 3 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m"
2. Function Deployment
# greeting-function.yaml apiVersion: fission.io/v1 kind: Function metadata: name: greeting-function namespace: fission spec: environment: name: java-env namespace: fission package: functionName: greeting-function deployment: type: url url: "https://github.com/your-org/fission-functions/releases/download/v1.0.0/greeting-function.jar" secrets: - name: database-secret configMaps: - name: app-config resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m" invokeStrategy: executionStrategy: "poolmgr" specializations: 2
3. HTTP Trigger
# http-trigger.yaml apiVersion: fission.io/v1 kind: HTTPTrigger metadata: name: greeting-trigger namespace: fission spec: host: "api.example.com" relativeurl: "/greet" method: GET functionref: name: greeting-function type: name createingress: true
4. Package Specification
# package.yaml apiVersion: fission.io/v1 kind: Package metadata: name: greeting-package namespace: fission spec: environment: name: java-env namespace: fission source: type: url url: "https://github.com/your-org/fission-functions/releases/download/v1.0.0/greeting-function.jar" deployment: type: url url: "https://github.com/your-org/fission-functions/releases/download/v1.0.0/greeting-function.jar" buildcmd: "mvn clean package"
Fission Client for Function Management
package com.example.fission.client;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.concurrent.TimeUnit;
/**
* Fission client for managing functions programmatically
*/
public class FissionClient {
private static final Logger logger = LoggerFactory.getLogger(FissionClient.class);
private final KubernetesClient kubernetesClient;
public FissionClient() {
this.kubernetesClient = new DefaultKubernetesClient();
}
public void deployFunction(String functionName, File jarFile, String environment) {
logger.info("Deploying function: {}", functionName);
try {
// Create package
createPackage(functionName, jarFile, environment);
// Create function
createFunction(functionName, environment);
// Create HTTP trigger
createHttpTrigger(functionName, "/" + functionName);
logger.info("Function deployed successfully: {}", functionName);
} catch (Exception e) {
logger.error("Failed to deploy function: {}", functionName, e);
throw new RuntimeException("Function deployment failed", e);
}
}
public void updateFunction(String functionName, File jarFile) {
logger.info("Updating function: {}", functionName);
try {
// Update package
updatePackage(functionName, jarFile);
logger.info("Function updated successfully: {}", functionName);
} catch (Exception e) {
logger.error("Failed to update function: {}", functionName, e);
throw new RuntimeException("Function update failed", e);
}
}
public void deleteFunction(String functionName) {
logger.info("Deleting function: {}", functionName);
try {
// Delete trigger
deleteHttpTrigger(functionName);
// Delete function
deleteFunctionResource(functionName);
// Delete package
deletePackage(functionName);
logger.info("Function deleted successfully: {}", functionName);
} catch (Exception e) {
logger.error("Failed to delete function: {}", functionName, e);
throw new RuntimeException("Function deletion failed", e);
}
}
public void invokeFunction(String functionName, String payload) {
logger.info("Invoking function: {}", functionName);
try {
// Get function endpoint
String endpoint = getFunctionEndpoint(functionName);
// Invoke function via HTTP
// Implementation would use HTTP client to call the endpoint
logger.info("Function invoked successfully: {}", functionName);
} catch (Exception e) {
logger.error("Failed to invoke function: {}", functionName, e);
throw new RuntimeException("Function invocation failed", e);
}
}
public void waitForFunctionReady(String functionName, long timeout, TimeUnit unit) {
logger.info("Waiting for function to be ready: {}", functionName);
long endTime = System.currentTimeMillis() + unit.toMillis(timeout);
while (System.currentTimeMillis() < endTime) {
try {
if (isFunctionReady(functionName)) {
logger.info("Function is ready: {}", functionName);
return;
}
Thread.sleep(1000); // Wait 1 second between checks
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Wait interrupted", e);
}
}
throw new RuntimeException("Function not ready within timeout: " + functionName);
}
// Private implementation methods
private void createPackage(String functionName, File jarFile, String environment) {
// Implementation using Kubernetes client or Fission CLI
logger.debug("Creating package for function: {}", functionName);
}
private void createFunction(String functionName, String environment) {
logger.debug("Creating function: {}", functionName);
}
private void createHttpTrigger(String functionName, String path) {
logger.debug("Creating HTTP trigger for function: {} at path: {}", functionName, path);
}
private void updatePackage(String functionName, File jarFile) {
logger.debug("Updating package for function: {}", functionName);
}
private void deleteHttpTrigger(String functionName) {
logger.debug("Deleting HTTP trigger for function: {}", functionName);
}
private void deleteFunctionResource(String functionName) {
logger.debug("Deleting function resource: {}", functionName);
}
private void deletePackage(String functionName) {
logger.debug("Deleting package: {}", functionName);
}
private String getFunctionEndpoint(String functionName) {
// Implementation to get function endpoint
return "http://router.fission.svc.cluster.local/" + functionName;
}
private boolean isFunctionReady(String functionName) {
// Implementation to check function readiness
return true; // Simplified
}
}
Testing Fission Functions
package com.example.fission.test;
import io.fission.Context;
import io.fission.Response;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for Fission functions
*/
class GreetingFunctionTest {
private GreetingFunction function;
@BeforeEach
void setUp() {
function = new GreetingFunction();
function.init();
}
@AfterEach
void tearDown() {
function.destroy();
}
@Test
void testGreetingWithName() {
// Given
Map<String, String> query = new HashMap<>();
query.put("name", "John");
Context context = new Context.Builder()
.method("GET")
.url("/greet")
.query(query)
.build();
// When
Response response = function.call(context);
// Then
assertEquals(200, response.getStatus());
assertTrue(response.getBody().contains("Hello, John!"));
assertEquals("application/json", response.getHeaders().get("Content-Type"));
}
@Test
void testGreetingWithoutName() {
// Given
Context context = new Context.Builder()
.method("GET")
.url("/greet")
.build();
// When
Response response = function.call(context);
// Then
assertEquals(200, response.getStatus());
assertTrue(response.getBody().contains("Hello, World!"));
}
@Test
void testHealthCheck() {
// Given
Context context = new Context.Builder()
.method("GET")
.url("/health")
.build();
// When
Response response = function.health(context);
// Then
assertEquals(200, response.getStatus());
assertTrue(response.getBody().contains("healthy"));
}
}
Best Practices
- Stateless Design: Functions should be stateless and idempotent
- Resource Management: Properly initialize and cleanup resources
- Error Handling: Comprehensive error handling and logging
- Configuration: Use environment variables for configuration
- Monitoring: Implement proper logging and metrics
- Security: Validate inputs and handle secrets properly
// Example of best practices in a function
public class BestPracticeFunction implements FissionFunction {
private static final Logger logger = LoggerFactory.getLogger(BestPracticeFunction.class);
@Override
public Response call(Context context) {
try {
// Input validation
if (!isValidRequest(context)) {
return Response.badRequest()
.body("{\"error\": \"Invalid request\"}")
.json()
.build();
}
// Process request
String result = processRequest(context);
// Return response
return Response.ok()
.body("{\"result\": \"" + result + "\"}")
.json()
.build();
} catch (Exception e) {
logger.error("Function execution failed", e);
return Response.error()
.body("{\"error\": \"Internal server error\"}")
.json()
.build();
}
}
private boolean isValidRequest(Context context) {
// Validate inputs
return context != null && context.getMethod() != null;
}
private String processRequest(Context context) {
// Business logic
return "processed";
}
}
Conclusion
Fission Java Functions provide:
- Serverless Execution: Event-driven function execution on Kubernetes
- Rapid Development: Simple function interface and deployment
- Scalability: Automatic scaling based on load
- Kubernetes Native: Leverages Kubernetes for orchestration
- Multi-language Support: Java, Node.js, Python, Go, etc.
- Production Ready: Built-in monitoring, logging, and security
This implementation enables developers to create robust, scalable serverless functions in Java that can handle various workloads from simple HTTP endpoints to complex data processing tasks, all while leveraging the power of Kubernetes orchestration.
Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/
OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/
OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/
Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.
https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics
Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2
Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide
Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2
Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide
Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.
https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server
Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.