We just shipped the BoxLang Google Cloud Functions Runtime β and it brings the same write-once-run-anywhere serverless experience you already know from our AWS Lambda runtime, now running natively on Google Cloud Functions Gen2.
The big idea is simple: the same .bx handler file you deploy to AWS Lambda works on GCF without modification. One codebase. Two clouds. Zero rewrites.
The Architecture
The GCF experience is intentionally split into two projects:
- boxlang-google-functions β The main runtime that publishes to Maven
- boxlang-starter-google-functions β your app shell, handlers, tests, and Gradle tasks
Your business logic lives entirely in BoxLang files under src/main/bx. Java is only the serverless bridge β you never touch it.
The runtime entry point is ortus.boxlang.runtime.gcp.FunctionRunner, which implements Google Cloud's HttpFunction interface. On cold start the BoxLang runtime initializes once. Compiled .bx classes are cached internally and reused across all warm invocations β you pay the compilation cost exactly once.
Documentation
Of course, everything is fully documented: https://boxlang.ortusbooks.com/getting-started/running-boxlang/google-cloud-functions
FIU Collaboration
This runtime was built in collaboration with Florida International University (FIU) and a talented team of students who contributed to making BoxLang on Google Cloud Functions a reality. We are incredibly grateful for their hard work and dedication to the BoxLang ecosystem. π
Writing Handlers
Handlers are plain BoxLang classes that expose a run() method:
class {
function run( event, context, response ) {
response.statusCode = 200
response.body = {
"error" : false,
"messages" : [],
"data" : "Hello from BoxLang on GCF!"
}
}
}
Every handler method receives three arguments:
| Argument | Type | Description |
|---|---|---|
event | Struct | All incoming request data β method, path, headers, body, query |
context | Struct | GCF metadata β function name, revision, project ID, request ID |
response | Struct | Mutable response struct β statusCode, headers, body, cookies |
Returning a struct or array from your method JSON-serializes it automatically. Returning a plain string writes it verbatim. Or set response.body directly for explicit control.
A single handler class can expose multiple methods:
class {
function run( event, context, response ) {
response.statusCode = 200
response.body = { "resource": "customers", "path": event.path }
}
function findById( event, context, response ) {
response.statusCode = 200
response.body = { "action": "findById", "path": event.path }
}
}
Two Layers of Routing
The runtime gives you two independent dispatch layers, which together let you build clean multi-resource APIs from a single function deployment.
Layer 1 β URI β Handler Class
The first URI path segment is converted to PascalCase and matched against .bx files in
your handler root. If no match is found, Lambda.bx is the fallback:
| Request URI | Resolved Handler |
|---|---|
/ | Lambda.bx |
/customers | Customers.bx |
/customers/123 | Customers.bx |
/products | Products.bx |
/user-profiles | UserProfiles.bx |
/unknown | Lambda.bx |
To add a route, just drop a PascalCase .bx file in src/main/bx. No config. No routing
tables. Convention does the work.
src/main/bx/
Application.bx
Lambda.bx β fallback + root route
Customers.bx β handles /customers/**
Products.bx β handles /products/**
UserProfiles.bx β handles /user-profiles/**
Layer 2 β x-bx-function β Method
Once the handler class is resolved, you can route to any method in it using the
x-bx-function request header. Without the header, run() is called by default:
# Calls Lambda.bx::run()
curl http://localhost:9099/
# Calls Lambda.bx::anotherLambda()
curl -H "x-bx-function: anotherLambda" http://localhost:9099/
# Calls Customers.bx::findById()
curl -H "x-bx-function: findById" http://localhost:9099/customers/123
URI path selects the class. x-bx-function selects the method. Two clean axes of dispatch.
The Event Struct β Portable By Design π―
This is the part worth paying attention to. The event struct your handler receives mirrors
the AWS API Gateway v2.0 HTTP event format β intentionally:
{
method : "GET",
path : "/products/42",
rawPath : "/products/42",
headers : { "content-type": "application/json" },
queryStringParameters : { page: "1" },
body : "",
requestContext : {
http : {
method : "GET",
path : "/products/42"
}
}
}
If you're already running BoxLang on AWS Lambda, your handlers drop into GCF with zero changes. Same struct shape. Same handler signature. Same return conventions.
Run Locally in Seconds
Clone the starter and you're running in three commands:
git clone https://github.com/ortus-boxlang/boxlang-starter-google-functions.git
cd boxlang-starter-google-functions
./gradlew clean test
./gradlew runFunction
Expected output:
================================================================
BoxLang GCF Function Invoker
Listening on : http://localhost:9099
Function root : .../src/main/bx
Debug mode : true
Press Ctrl+C to stop.
================================================================
The runFunction task uses the official Google Functions Java Invoker under the hood and
wires it to FunctionRunner automatically. Local overrides are available as Gradle flags:
./gradlew runFunction -PtestPort=8080 # custom port
./gradlew runFunction -PdebugMode=true # verbose logging + live reload
./gradlew runFunction -PfunctionRoot=/path # custom handler directory
Enable debugMode and .bx file changes are picked up live β no JAR rebuild, no restart.
Never enable debug mode in production β it disables class caching.
Smoke Testing Locally
# Default handler
curl http://localhost:9099/
# POST with JSON body
curl -X POST http://localhost:9099/ \
-H "Content-Type: application/json" \
-d @workbench/sampleRequests/event-local.json
# Method routing
curl -H "x-bx-function: anotherLambda" http://localhost:9099/
Testing
The starter ships with a full integration test suite using JUnit and Google Truth. Tests run against real GCF mock HTTP objects β no mocking frameworks:
| Test Class | Coverage |
|---|---|
FunctionRunnerTest | Request lifecycle, URI routing, method routing, JSON responses, concurrency |
MockHttpRequest | Fluent test request builder |
MockHttpResponse | Captures status, body, and headers |
./gradlew test
# Run just the integration suite
./gradlew test --tests "com.myproject.FunctionRunnerTest"
# Open the HTML report
open build/reports/tests/test/index.html
Build and Deploy
Build the Deployable Package
./gradlew clean shadowJar buildLambdaZip
This produces a deployable ZIP at:
build/distributions/boxlang-google-function-project-<version>.zip
The ZIP contains everything GCF needs β .bx handlers at the package root, boxlang.json,
boxlang_modules/, and the runtime JAR with all dependencies in lib/.
Deploy to Google Cloud Functions Gen2
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
gcloud functions deploy YOUR_FUNCTION_NAME \
--gen2 \
--runtime=java21 \
--region=us-central1 \
--entry-point=ortus.boxlang.runtime.gcp.FunctionRunner \
--trigger-http \
--allow-unauthenticated \
--source=build/distributions/boxlang-google-function-project-1.0.0.zip
If you changed
versioningradle.properties, update the ZIP filename in--sourceaccordingly.
Environment Variables
| Variable | Description |
|---|---|
BOXLANG_GCP_ROOT | Root directory for .bx handlers |
BOXLANG_GCP_CLASS | Override default handler path |
BOXLANG_GCP_DEBUGMODE | Enable verbose logging and disable class caching |
BOXLANG_GCP_CONFIG | Custom boxlang.json path |
K_SERVICE | Function name (set automatically by GCF Gen2) |
K_REVISION | Function revision (set automatically by GCF Gen2) |
GOOGLE_CLOUD_PROJECT | Project ID (set automatically by GCF Gen2) |
BoxLang Modules Work Too
Any BoxLang module works with this runtime. Include it in your deployment ZIP under
boxlang_modules/ and point BOXLANG_GCP_CONFIG at your boxlang.json. The entire module
ecosystem β database, caching, AI, and more β is available inside your serverless functions.
Get Started
π Starter Template: github.com/ortus-boxlang/boxlang-starter-google-functions
π¦ Runtime Source: github.com/ortus-boxlang/boxlang-google-functions
π BoxLang Docs: boxlang.ortusbooks.com
BoxLang is built to run everywhere β web servers, AWS Lambda, Google Cloud Functions, WebAssembly, mobile, and more. Every new runtime we ship gets you closer to a world where your application logic outlives any single cloud vendor's pricing model.
Write once. Run anywhere. Ship faster. β‘
Add Your Comment