Blog

Luis Majano

April 09, 2026

Spread the word


Share your thoughts

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:

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:

ArgumentTypeDescription
eventStructAll incoming request data — method, path, headers, body, query
contextStructGCF metadata — function name, revision, project ID, request ID
responseStructMutable 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 URIResolved Handler
/Lambda.bx
/customersCustomers.bx
/customers/123Customers.bx
/productsProducts.bx
/user-profilesUserProfiles.bx
/unknownLambda.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 ClassCoverage
FunctionRunnerTestRequest lifecycle, URI routing, method routing, JSON responses, concurrency
MockHttpRequestFluent test request builder
MockHttpResponseCaptures 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 version in gradle.properties, update the ZIP filename in --source accordingly.


Environment Variables

VariableDescription
BOXLANG_GCP_ROOTRoot directory for .bx handlers
BOXLANG_GCP_CLASSOverride default handler path
BOXLANG_GCP_DEBUGMODEEnable verbose logging and disable class caching
BOXLANG_GCP_CONFIGCustom boxlang.json path
K_SERVICEFunction name (set automatically by GCF Gen2)
K_REVISIONFunction revision (set automatically by GCF Gen2)
GOOGLE_CLOUD_PROJECTProject 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

Recent Entries

MatchBox Web Server: BoxLang Without the Full Server Stack

MatchBox Web Server: BoxLang Without the Full Server Stack

MatchBox includes a native web runtime for building small, fast BoxLang web applications without requiring a JVM or a traditional servlet container. You can follow the MatchBox open beta at https://github.com/ortus-boxlang/matchbox. There are two related pieces in the beta today: a webroot server for .bxm templates and static assets, and a routed app server built around web.server(). Both are early, but they show the direction clearly: BoxLang should be able to serve HTTP from a compact native runtime when the application does not need the full JVM stack.

Victor Campos
Victor Campos
July 07, 2026
Ortus & BoxLang June Recap 2026

Ortus & BoxLang June Recap 2026

July was packed with exciting advancements across the Ortus ecosystem, bringing new BoxLang releases, powerful developer tools, and continued innovation in AI and multi-runtime development.

Victor Campos
Victor Campos
July 06, 2026
Ortus Solutions and BoxLang at CFCamp 2026: Platinum Sponsor, Keynote Leaders, and a Full Lineup of Innovation

Ortus Solutions and BoxLang at CFCamp 2026: Platinum Sponsor, Keynote Leaders, and a Full Lineup of Innovation

CFCamp 2026 was an important milestone for the Ortus Solutions team and for the growing BoxLang ecosystem.

This year, Ortus Solutions participated as a Platinum Sponsor and had the honor of leading the official Keynote, where Luis Majano, Brad Wood, and Jacob Beers shared major updates around ColdBox, BoxLang, AI, and multi-runtime support.

The message was clear: the CFML ecosystem is not standing still. With BoxLang, ColdBox, C...

Cristobal Escobar
Cristobal Escobar
July 02, 2026