Serverless and Cloud Functions
|
This section documents Quarkus 3.39.x (current) with 3.33.x LTS as the production recommendation, on the Java 17+ baseline, as published at the official Quarkus guides — which are the reference these pages are written and verified against. Quarkus 4 raises the baseline to Java 21 (first release candidate planned for end of September 2026, see the Quarkus blog); readers upgrading past 3.39.x/3.33.x LTS should expect that change. This content was generated with the assistance of AI and should be verified against those official guides before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Quarkus runs the same programming model — CDI beans, Quarkus REST resources, reactive routes — whether the process is a long-running server or a single function invocation triggered by a cloud provider’s event system. This page covers the three major FaaS platforms Quarkus has first-class extensions for — AWS Lambda, Google Cloud Functions, and Azure Functions — each in a plain variant (a handler implementing the platform’s own function interface) and, where available, an HTTP variant (the platform’s event source fronting a full Quarkus web application unchanged), plus Funqy, Quarkus’s own portable function abstraction that targets several of these platforms and Knative Events from a single annotated method. It closes with cold-start behavior across the JVM, JVM-plus-SnapStart, and native-executable paths, since cold start is the concern that most shapes which of these options to reach for.
AWS Lambda
AWS Lambda’s Java runtime starts a JVM (or, for a native executable, the custom runtime process directly) inside
a managed execution environment, invokes a configured handler once per event, and freezes or destroys that
environment between invocations depending on traffic. Quarkus integrates with this model through the
quarkus-amazon-lambda extension rather than requiring hand-written bootstrap code:
./mvnw quarkus:add-extension -Dextensions=amazon-lambda
Plain handler
A plain Lambda function implements AWS’s own com.amazonaws.services.lambda.runtime.RequestHandler<I, O>
interface; Quarkus discovers the single CDI bean implementing it and wires it as the function’s entry point, so
the only application code needed is the handler class itself:
package com.example.function;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import jakarta.inject.Named;
@Named("greeting")
public class GreetingHandler implements RequestHandler<GreetingRequest, GreetingResponse> {
@Override
public GreetingResponse handleRequest(GreetingRequest request, Context context) {
return new GreetingResponse("Hello, " + request.name() + "!");
}
}
record GreetingRequest(String name) {
}
record GreetingResponse(String message) {
}
application.properties names which bean handles invocations when more than one RequestHandler is present on
the classpath, and sets the deployed function’s own AWS-side name for sam/CDK/CloudFormation tooling to refer
to:
quarkus.lambda.handler=greeting
@Named on the handler class and quarkus.lambda.handler must match; with only one RequestHandler bean in
the whole application, the property can be omitted and Quarkus resolves it unambiguously on its own.
HTTP variant
quarkus-amazon-lambda-http swaps the plain-handler model for one where API Gateway (HTTP API or REST API) or a
Lambda Function URL forwards the raw HTTP request to Quarkus’s own HTTP layer, letting a Quarkus REST resource,
a reactive route, or an Undertow-based servlet run behind Lambda unchanged — no RequestHandler, no
Lambda-specific request/response types anywhere in application code:
./mvnw quarkus:add-extension -Dextensions=amazon-lambda-http
The REST resource itself is written exactly as described in Quarkus
REST — @Path, @GET, entity parameters, @Blocking/non-blocking dispatch, exception mappers, all of it
apply identically. The only Lambda-specific piece is application.properties telling the extension which event
source shape to parse:
# One of: API_GATEWAY, API_GATEWAY_REST, ALB, or omit for a Lambda Function URL
quarkus.lambda-http.enable-polling=false
In practice most projects need no properties at all here beyond adding the extension — quarkus-amazon-lambda-http
auto-detects the incoming event shape (API Gateway HTTP API v2, API Gateway REST API v1, an Application Load
Balancer target, or a Function URL) at runtime, so the same deployed artifact works unchanged across any of
them.
Native vs. JVM Lambda
A Lambda handler can be packaged either as a JVM artifact running on AWS’s java21/java17 managed runtime, or
as a Quarkus native executable run through AWS’s provided.al2023 custom runtime. The native path starts a
real, standalone OS process instead of a JVM, and reaches steady state dramatically faster — see
the native executables page for how that build works and what it
costs in build time and library compatibility. For a Lambda function whose traffic pattern includes frequent
cold starts (bursty, low-traffic, or scale-to-zero workloads), native is usually the single biggest cold-start
lever available, ahead of any other tuning covered on this page.
SnapStart
AWS Lambda SnapStart takes a different approach to the same problem on the JVM path: instead of starting a fresh JVM on every cold invocation, AWS initializes one execution environment when a function version is published, takes a snapshot of that initialized process (memory and disk state included), and restores new execution environments from that snapshot rather than booting a JVM from scratch. Enabling it is a deployment concern, not application code — in a SAM template or CDK/CloudFormation resource:
GreetingFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: java21
Handler: com.example.function.GreetingHandler
SnapStart:
ApplyOn: PublishedVersions
SnapStart and Quarkus are complementary rather than redundant: Quarkus’s build-time augmentation (see Build-Time Augmentation and the Closed World) already minimizes what runtime init has to do, so the state SnapStart snapshots is already close to minimal; SnapStart then removes the remaining JVM-startup-and-class-loading cost on top of that by not repeating it per cold environment at all. Two Quarkus-side considerations matter specifically because a snapshot freezes state at publish time and reuses it across many later invocations:
-
Avoid capturing non-deterministic or invocation-specific state during static/runtime init that would be wrong if reused verbatim across every environment restored from the same snapshot — a random seed, a generated unique ID, or a timestamp read once at startup and cached, for example. AWS’s own SnapStart guidance recommends a Lambda runtime hook (
beforeCheckpoint/afterRestore) for anything that must be regenerated fresh per restored environment rather than baked into the snapshot. -
Any connection opened during init (a database connection, an HTTP client’s pooled sockets) is frozen into the snapshot and may no longer be valid once restored minutes or hours later; such resources are better initialized lazily, on first actual use, than eagerly at startup when SnapStart is in play.
For Lambda itself on AWS — runtimes, concurrency, SnapStart and limits — see AWS Lambda. For Cloud Run functions and their event triggers on Google Cloud, see Cloud Run Functions and Events.
Google Cloud Functions and Azure Functions
Google Cloud Functions
quarkus-google-cloud-functions targets a plain function implementing one of Google’s own functional interfaces — com.google.cloud.functions.HttpFunction for an HTTP-triggered function, or BackgroundFunction<T> for an
event-triggered one (Pub/Sub, Cloud Storage, and so on):
package com.example.function;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
public class GreetingFunction implements HttpFunction {
@Override
public void service(HttpRequest request, HttpResponse response) throws IOException {
response.getWriter().write("Hello from Cloud Functions!");
}
}
quarkus-google-cloud-functions-http is the HTTP-adapter equivalent of the Lambda HTTP variant above: it runs a
full Quarkus REST/reactive-routes/Undertow application behind Cloud Functions' HTTP trigger with no
Cloud-Functions-specific code in the resource itself, using the same
Quarkus REST resources as any other deployment target.
Azure Functions
Azure Functions' Java worker invokes a method per function, discovered via the @FunctionName annotation and an
HttpTrigger/other binding parameter from the com.microsoft.azure.functions.annotation.* API — Quarkus’s
quarkus-azure-functions-http extension supplies that entry-point method for you and routes the incoming
request into Quarkus’s own HTTP layer, so, again, a Quarkus REST resource runs unchanged behind it. The practical
difference from the AWS/GCP HTTP variants is packaging: an Azure Functions Java app is deployed as a function
app project (host.json, local.settings.json, a function.json-generating build) rather than a bare JAR, and
the Azure Functions Core Tools (func) drive local run and deploy rather than a generic java -jar.
There is no plain (non-HTTP-adapter) Azure Functions extension analogous to
quarkus-amazon-lambda/quarkus-google-cloud-functions for arbitrary non-HTTP triggers — for a Timer, Queue,
or Event Grid trigger on Azure, Quarkus’s function-oriented option is Funqy’s Azure binding, covered next. For
the Azure Functions platform side — triggers and bindings, hosting plans and the isolated worker model — see
App Service and Azure Functions.
Funqy: a portable function API
Funqy is Quarkus’s own abstraction over "a function that takes an input and returns an output", independent of
which platform ends up invoking it: a plain Java method annotated io.quarkus.funqy.Funqy, with no
platform-specific interface or annotation in the method itself, that a separate binding extension exposes to a
particular trigger mechanism:
package com.example.function;
import io.quarkus.funqy.Funqy;
public class GreetingFunctions {
@Funqy
public GreetingResponse greet(GreetingRequest request) {
return new GreetingResponse("Hello, " + request.name() + "!");
}
}
record GreetingRequest(String name) {
}
record GreetingResponse(String message) {
}
The same @Funqy method becomes invocable from a given trigger purely by adding the matching binding extension,
with no change to GreetingFunctions itself:
| Binding extension | Exposes the @Funqy method as |
|---|---|
|
A plain HTTP POST endpoint, for local testing or any platform that just needs an HTTP entry point without a cloud-specific event envelope. |
|
An AWS Lambda handler — an alternative to implementing |
|
A Google Cloud Function, in place of implementing |
|
An Azure Function trigger — the option to reach for on Azure when the trigger is not HTTP (Timer, Queue, and
so on), since |
|
A CloudEvents-consuming Knative Serving/Eventing function, for Kubernetes-native event-driven deployments rather than a proprietary cloud FaaS platform. |
Choosing between a platform-specific extension (quarkus-amazon-lambda, quarkus-google-cloud-functions, and so
on) and Funqy is mostly a portability trade-off: the platform-specific extension gives direct access to that
platform’s own request/context types (AWS’s Context, for instance) when the function genuinely needs them,
while Funqy trades that direct access away in exchange for the same method body running behind several different
triggers without modification.
Deploying to Azure, GCP, and Heroku
Deployment mechanics differ by platform, but in every case the Quarkus extension’s job stops at producing a
correctly packaged, correctly configured artifact — the actual deploy goes through that platform’s own standard
CLI or plugin tooling rather than anything Quarkus-specific: the Azure Functions extension integrates with the
Azure Functions Core Tools / azure-functions-maven-plugin deploy flow, the Google Cloud Functions extensions
integrate with the gcloud functions deploy flow (or the Cloud Functions/Cloud Run Maven plugins), and a plain
Quarkus JAR or container image deploys to Heroku the same way any JVM or container-based Heroku app does — via
the Heroku CLI or a container registry push, with no Quarkus-specific deploy step involved. None of these
require code changes beyond picking the right extension; they differ in build/packaging output and in which
platform CLI drives the actual upload.
Cold start compared
The three JVM-adjacent execution paths covered above — a plain JVM Lambda, a JVM Lambda with SnapStart, and a native executable — differ in where the cold-start cost sits, not merely in how large it is. Exact timings vary by memory allocation, package size, and workload and are deliberately not quoted here; the diagram below shows relative ordering only.
A plain JVM Lambda pays JVM startup, class loading, and Quarkus’s runtime init on every cold environment. SnapStart removes the JVM-startup-and-class-loading portion by restoring an already-initialized process, leaving only whatever runtime hooks are needed to fix up state that cannot be safely reused verbatim (see the SnapStart considerations above). Native starts an already-compiled OS process directly, with no JVM and no class loading step of any kind, which is why it remains the fastest option even against a well-tuned SnapStart setup — at the cost of the native build’s own constraints, covered on the native executables page.