How does the same function bean deploy to AWS Lambda and Azure Functions without changing the business logic?
answer
- AWS handler = org.springframework.cloud.function.adapter.aws.FunctionInvoker
- Azure = adapter + @FunctionName thin handler -> catalog
- adapter maps event source -> function input
- context booted once, reused warm
- cold start -> GraalVM native image
basics
~20 sYou add a FaaS adapter dependency and point the cloud runtime at a generic handler that delegates to your function. For AWS you configure the handler as Spring Cloud Function's FunctionInvoker; for Azure you use the Azure adapter with an @FunctionName entry that calls into the FunctionCatalog. Your Function bean is unchanged.
solid answer
~40 sThe business logic stays a plain Function/Supplier/Consumer bean. A FaaS adapter bridges the cloud's event model to it. For AWS, you add spring-cloud-function-adapter-aws and set the Lambda handler to org.springframework.cloud.function.adapter.aws.FunctionInvoker — this generic handler boots the Spring context once, looks up the target function (via spring.cloud.function.definition) in the FunctionCatalog, converts the incoming event (S3, SQS, API Gateway, etc.) to your input type, and serializes the output. For Azure, you add spring-cloud-function-adapter-azure and write a thin handler class annotated with @FunctionName whose method receives the Azure trigger binding and delegates to the catalog. In both cases the framework owns transport and serialization; you own only the function. Cold starts, context reuse, and native-image (GraalVM) optimization are the main operational concerns.
code
java · 25 lines// Business logic — identical across web, stream, AWS, Azure.
@SpringBootApplication
public class OrderApp {
@Bean
public Function<Order, Receipt> processOrder() {
return order -> new Receipt(order.id(), order.total());
}
}
// AWS: NO custom handler class needed.
// In the Lambda config set:
// Handler = org.springframework.cloud.function.adapter.aws.FunctionInvoker
// Env = SPRING_CLOUD_FUNCTION_DEFINITION=processOrder
// Dependency: org.springframework.cloud:spring-cloud-function-adapter-aws
// Azure: a thin @FunctionName handler delegates into the catalog.
public class ProcessOrderHandler extends FunctionInvoker<Order, Receipt> {
@FunctionName("processOrder")
public Receipt run(
@HttpTrigger(name = "req", methods = {HttpMethod.POST})
HttpRequestMessage<Order> request,
ExecutionContext context) {
return handleRequest(request.getBody(), context);
}
}go deeper
Knows an adapter dependency plus a generic handler makes a function run on Lambda.
Names FunctionInvoker for AWS and the Azure @FunctionName delegation, and that logic is unchanged.
Explains event-source mapping, context reuse, cold starts, and native-image mitigation.
Weighs FaaS portability against operational cost and native-image build complexity across the org.
**The portability contract:** your logic is a `Function`/`Supplier`/`Consumer` bean. FaaS **adapters** translate a cloud provider's invocation model into a call on that bean, so the business code never imports provider SDK types (or does so only at the thin edge). This is the payoff of decoupling logic from transport. **AWS Lambda — `spring-cloud-function-adapter-aws`:** - Add the dependency and build a deployable jar (typically a shaded/thin jar). - In the Lambda configuration, set the **Handler** to the framework's generic entry point: **`org.springframework.cloud.function.adapter.aws.FunctionInvoker`** (fully qualified). You do *not* write your own `RequestHandler`. - `FunctionInvoker` starts the Spring `ApplicationContext` on the first (cold) invocation, resolves the target function from the **`FunctionCatalog`** using **`spring.cloud.function.definition`**, and reuses the context across warm invocations. - It maps common AWS event sources — API Gateway (proxy request/response), SQS, SNS, S3, Kinesis, CloudWatch scheduled events — into your function's input type, applying message converters. For API Gateway it can produce the proper HTTP response envelope. - If your function needs the raw event/headers, declare `Function<Message<MyPayload>, ...>` to access headers, or accept the specific AWS event POJO. **Azure Functions — `spring-cloud-function-adapter-azure`:** - Azure's programming model requires a handler method annotated with **`@FunctionName`** and parameters annotated with trigger/binding annotations (e.g. `@HttpTrigger`, `@BlobTrigger`). - You write a *thin* handler class that extends the adapter's base support (historically `AzureSpringBootRequestHandler`, later `FunctionInvoker`-style base) and, inside the `@FunctionName` method, delegates the input to the Spring function resolved from the `FunctionCatalog`. The business logic itself is still your plain bean. - The Azure Maven plugin packages and deploys the function app. **Web and Stream for comparison (the "uniform deployment" claim):** the *same* bean can also run as: - **Web:** `spring-cloud-starter-function-web` exposes it at `/<name>` over HTTP. - **Stream:** `spring-cloud-stream` binds it to Kafka/RabbitMQ using the functional model (the binder reads `spring.cloud.function.definition`). So one implementation targets web, stream, and both major FaaS platforms by swapping dependencies/config — not code. **Operational gotchas:** - **Cold starts:** the JVM + Spring context boot dominates first-call latency. Mitigate with GraalVM **native image** (Spring Native / Spring Boot AOT), provisioned concurrency (AWS), or keeping the deployment small. - **Context reuse:** the context is created once and reused on warm invocations — keep beans stateless and thread-safe; don't rely on per-request singletons holding request state. - **Handler misconfiguration:** pointing Lambda at your own class instead of `FunctionInvoker` is a frequent mistake; the adapter handler is what wires the catalog. - **Multiple functions:** with several beans, set `spring.cloud.function.definition` (or use routing) so the adapter knows which to invoke. - **Payload shape:** API Gateway proxy integration expects a specific response structure; let the adapter handle it rather than hand-rolling. **When to use:** genuine multi-target or serverless requirements, or a desire to keep the door open to FaaS. **When not:** a single long-running web service with no serverless plans gains little and pays the abstraction cost.
- Why are cold starts a bigger concern for Spring on Lambda, and how do you reduce them?Because each cold invocation must start the JVM and the Spring ApplicationContext, adding latency. Mitigations: compile to a GraalVM native image via Spring Boot AOT, use AWS provisioned concurrency, trim the classpath, and keep the context reused on warm invocations.
- How does the adapter know which function to run when several beans exist?Via spring.cloud.function.definition (property or SPRING_CLOUD_FUNCTION_DEFINITION env var), or the built-in RoutingFunction for per-message dispatch. The adapter resolves that name from the FunctionCatalog.
saying these in an interview costs you the question
- Saying you must write a custom AWS RequestHandler instead of using FunctionInvoker
- Claiming the business logic must import AWS/Azure SDK types
- Ignoring cold starts / assuming the context reboots on every warm call
- Thinking a different function implementation is needed per platform