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

quarkus-funqy-http

A plain HTTP POST endpoint, for local testing or any platform that just needs an HTTP entry point without a cloud-specific event envelope.

quarkus-funqy-amazon-lambda

An AWS Lambda handler — an alternative to implementing RequestHandler directly when the function should stay portable across platforms.

quarkus-funqy-google-cloud-functions

A Google Cloud Function, in place of implementing HttpFunction/BackgroundFunction directly.

quarkus-funqy-azure-functions

An Azure Function trigger — the option to reach for on Azure when the trigger is not HTTP (Timer, Queue, and so on), since quarkus-azure-functions-http only covers HTTP triggers.

quarkus-funqy-knative-events

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.

flowchart TD subgraph JVM["Plain JVM Lambda -- slowest"] direction TB A1[JVM process starts] --> A2[Class loading] A2 --> A3[Quarkus static init\nreplays recorded bytecode] A3 --> A4[Quarkus runtime init\nreal config, connections] A4 --> A5[Handler invoked] end subgraph SNAP["JVM + SnapStart -- fast"] direction TB B1[Snapshot restored\nfrom previously initialized JVM] --> B2[Runtime hook:\nafterRestore fix-ups] B2 --> B3[Handler invoked] end subgraph NATIVE["Native executable -- fastest"] direction TB C1[OS process starts\nno JVM, no class loading] --> C2[Runtime init\nalready-compiled machine code] C2 --> C3[Handler invoked] end

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.