Serverless Architecture
Serverless architecture is a way of building and running applications without managing servers yourself. You write small pieces of code, upload them to a cloud provider, and the provider runs them whenever they are needed, adding capacity automatically when traffic rises and removing it when traffic stops. You pay only for the work your code actually does.
This tutorial explains what serverless really means, how it works, its building blocks, cold starts, how it compares with traditional servers and containers, and when it is (and is not) a good choice. It also shows practical, formatted Java examples using AWS Lambda and Spring Cloud Function.
1. What Is Serverless Architecture?
Serverless architecture is a cloud computing model in which applications run in response to events and scale automatically. Instead of running a server all day, you write small, single-purpose pieces of code called functions. A function runs only when something triggers it, such as an HTTP request, a file upload or a new message in a queue, and stops when its work is done.
The cloud provider takes care of everything below your code: the servers, the operating system, the runtime, scaling, patching and availability.
2. Why Is It Called "Serverless"?
The name is a little misleading: servers still exist. "Serverless" means you do not see or manage them. You never choose a machine size, install an operating system or decide how many servers to run. From a developer's point of view, the servers have disappeared, even though the provider still runs plenty of them behind the scenes.
3. How Serverless Architecture Works
- Write a function that does one specific job, such as "resize an image" or "create an order".
- Connect it to an event source, such as an HTTP endpoint, a storage bucket, a queue or a schedule.
- An event happens, for example a user uploads a photo.
- The provider runs the function, starting as many copies as needed to handle all the events at once.
- The function finishes and returns a result or writes to another service. When there are no events, nothing runs and you pay nothing for compute.
4. Key Components of Serverless Architecture
| Component | What it does | Examples |
|---|---|---|
| Functions | Small, single-purpose pieces of code that run on demand | AWS Lambda, Azure Functions |
| API Gateway | Turns HTTP requests into function calls; handles routing, authentication and throttling | Amazon API Gateway, Azure API Management |
| Event sources | Things that trigger functions | File uploads, queues, database changes, schedules |
| Managed data stores | Databases and storage that scale without servers | Amazon DynamoDB, Amazon S3, Azure Cosmos DB |
| Messaging | Connects functions and services asynchronously | Amazon SQS and SNS, Amazon EventBridge |
| Identity and permissions | Controls what each function is allowed to access | AWS IAM roles, Azure managed identities |
| Monitoring | Collects logs, metrics and traces | Amazon CloudWatch, AWS X-Ray, Azure Monitor |
5. FaaS vs BaaS
Serverless covers two related ideas:
- FaaS (Function as a Service): you write the code, and the platform runs it in response to events. AWS Lambda and Azure Functions are FaaS.
- BaaS (Backend as a Service): ready-made backend features you use directly, without writing or running server code, such as authentication, databases, file storage and push notifications. Firebase and Amazon Cognito are common examples.
Most serverless applications combine both: functions hold the business logic, and managed services provide the rest.
6. Serverless vs Traditional Servers vs Containers
| Aspect | Traditional servers / VMs | Containers | Serverless functions |
|---|---|---|---|
| You manage | OS, runtime, scaling, patching, code | Container images, orchestration settings, code | Code and configuration only |
| Scaling | Manual or auto-scaling groups | Orchestrator scales containers | Automatic per request, including to zero |
| Billing | Per server, running or idle | Per node or container, running or idle | Per request and execution time |
| Startup time | Minutes | Seconds | Milliseconds to a few seconds (cold start) |
| Long-running work | Yes | Yes | Limited (for example, 15 minutes per call on AWS Lambda) |
| Control | Full | High | Limited |
| Best for | Legacy apps, special hardware | Microservices with steady traffic | Event-driven work, spiky or low traffic |
7. Function Lifecycle and Cold Starts
When a function is called and no ready copy exists, the provider must first prepare one: start a small environment, load the runtime and your code, and run your initialisation logic. This delay is called a cold start. Later calls reuse the prepared copy and skip these steps; these are warm starts.
Cold starts are often a few hundred milliseconds, and can be longer for large applications or runtimes like the JVM. Ways to reduce them:
- Keep packages small and avoid heavy work during initialisation.
- Create clients once, outside the handler method, so warm calls reuse them.
- Give the function more memory; providers also give it more CPU, so initialisation finishes faster.
- Use provisioned concurrency to keep a number of copies ready in advance, for latency-sensitive endpoints.
- For Java on AWS Lambda, enable SnapStart, which starts new copies from a snapshot of an already initialised environment, or compile to a native image with GraalVM.
8. Writing a Serverless Function in Java
On AWS Lambda, a Java function is a class that implements RequestHandler. Add the Lambda libraries to your Maven project (check Maven Central for the latest versions):
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.4</version>
</dependency>
This function handles an HTTP request coming through API Gateway and returns a JSON greeting:
package com.javabytechie.serverless;
import java.util.Map;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
public class HelloHandler
implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request,
Context context) {
// Read an optional query parameter: /hello?name=Asha
Map<String, String> params = request.getQueryStringParameters();
String name = (params != null && params.get("name") != null) ? params.get("name") : "World";
context.getLogger().log("Greeting " + name + ", request id " + context.getAwsRequestId());
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(Map.of("Content-Type", "application/json"))
.withBody("{\"message\": \"Hello, " + name + "!\"}");
}
}
Package the code as a JAR, create a Lambda function with a Java runtime (such as Java 21), set the handler to com.javabytechie.serverless.HelloHandler::handleRequest, and connect it to an API Gateway route. Each HTTP request now runs this method, and AWS adds more copies automatically as traffic grows.
9. Event-Driven Example: Processing Uploaded Files
Functions are not just for HTTP. This one runs automatically whenever a file is uploaded to an Amazon S3 bucket, for example to record new invoices. Notice that the client is created once, outside the handler, so warm calls reuse it:
package com.javabytechie.serverless;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord;
public class UploadProcessor implements RequestHandler<S3Event, String> {
// Created once per environment and reused by every warm invocation
private final InvoiceRepository invoices = new InvoiceRepository();
@Override
public String handleRequest(S3Event event, Context context) {
LambdaLogger logger = context.getLogger();
int processed = 0;
for (S3EventNotificationRecord record : event.getRecords()) {
String bucket = record.getS3().getBucket().getName();
String key = record.getS3().getObject().getUrlDecodedKey(); // e.g. "invoices/inv-1001.pdf"
long size = record.getS3().getObject().getSizeAsLong();
if (!key.endsWith(".pdf")) {
logger.log("Skipping non-PDF file: " + key);
continue;
}
// Idempotent: saving the same key twice does not create a duplicate
invoices.saveIfAbsent(bucket, key, size);
processed++;
}
logger.log("Processed " + processed + " file(s)");
return "OK";
}
}
Events can be delivered more than once, so the function is idempotent: processing the same file twice has the same effect as processing it once.
10. Serverless with Spring Boot: Spring Cloud Function
If you prefer Spring, Spring Cloud Function lets you write business logic as plain Java functions and run the same code on AWS Lambda, Azure Functions or as a normal web application. Your code does not depend on any one provider's API:
package com.javabytechie.serverless;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.function.Function;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class OrderFunctions {
public record OrderRequest(String productId, int quantity, BigDecimal unitPrice) { }
public record OrderQuote(String productId, int quantity, BigDecimal total) { }
public static void main(String[] args) {
SpringApplication.run(OrderFunctions.class, args);
}
/** Calculates the total price of an order, including 18% tax. */
@Bean
public Function<OrderRequest, OrderQuote> quoteOrder() {
return request -> {
if (request.quantity() <= 0) {
throw new IllegalArgumentException("Quantity must be positive");
}
BigDecimal total = request.unitPrice()
.multiply(BigDecimal.valueOf(request.quantity()))
.multiply(new BigDecimal("1.18"))
.setScale(2, RoundingMode.HALF_UP);
return new OrderQuote(request.productId(), request.quantity(), total);
};
}
}
To deploy on AWS Lambda, add the spring-cloud-function-adapter-aws dependency and set the Lambda handler to org.springframework.cloud.function.adapter.aws.FunctionInvoker. Locally, with the web starter, the same function is available as an HTTP endpoint, which makes testing easy.
11. Designing Serverless Applications
- Keep functions small and focused. One function, one job, such as "resize image" or "create order".
- Make functions stateless. A copy may be created or destroyed at any time, so never keep important data in memory between calls. Store state in a database, cache or storage service.
- Make functions idempotent. Events can arrive more than once; use unique IDs so repeats are harmless.
- Prefer asynchronous flows. Use queues and events between functions instead of long chains of direct calls.
- Set timeouts and retries thoughtfully, and send messages that keep failing to a dead-letter queue so they can be inspected later.
- Protect downstream systems. Thousands of parallel copies can overwhelm a database, so limit concurrency or use connection pooling services such as Amazon RDS Proxy.
- Grant least privilege. Give each function only the permissions it needs.
12. How Serverless Pricing Works
Most FaaS platforms charge for two things:
- Number of requests: a small charge per million invocations.
- Compute time: how long your code runs multiplied by the memory you assigned, measured in GB-seconds.
Requests per month : 3,000,000
Memory per function: 512 MB = 0.5 GB
Average run time : 200 ms = 0.2 s
Compute usage = 3,000,000 x 0.2 s x 0.5 GB = 300,000 GB-seconds per month
Monthly bill = (requests x price per request) + (GB-seconds x price per GB-second)
Prices differ by provider and region, and many offer a monthly free allowance, so check the current price list. The key point is that idle time costs nothing. For workloads that run at high, steady load all day, however, always-on servers or containers can become cheaper than paying per request.
13. Advantages of Serverless Architecture
- No server management: no machines to provision, patch or monitor.
- Automatic scaling: from zero to thousands of parallel executions, and back down, without configuration.
- Pay for use: you are charged only when code runs, which suits spiky or low traffic.
- Faster development: teams focus on business logic and ship small features quickly.
- Built-in availability: providers run functions across multiple data centres.
- Natural fit for events: integrations with storage, queues, databases and schedules come ready-made.
14. Disadvantages and Limitations
- Cold starts: the first request to a new copy can be noticeably slower.
- Execution time limits: functions must finish within a maximum time, such as 15 minutes per invocation on AWS Lambda.
- Vendor lock-in: triggers, permissions and managed services are provider-specific, which makes moving harder.
- Harder debugging and testing: work is spread across many small functions and managed services, so you need good logging and distributed tracing.
- Limited control: you cannot tune the operating system or keep long-lived connections and background threads.
- Cost at steady high load: constant heavy traffic can cost more than reserved servers or containers.
15. When to Use Serverless (and When Not To)
| Good fit | Poor fit |
|---|---|
| Traffic that is unpredictable, spiky or often low | Constant, very high traffic around the clock |
| Event-driven tasks: uploads, messages, schedules | Long-running jobs that exceed time limits |
| New products and prototypes that must ship quickly | Applications needing special hardware or OS control |
| Glue code connecting cloud services | Workloads needing consistently very low latency where cold starts are unacceptable |
| Small teams without operations staff | Systems that must run on several clouds or on-premises without changes |
16. Real-World Use Cases
- REST and GraphQL APIs behind an API gateway for web and mobile apps.
- File processing: resizing images, creating thumbnails, scanning documents and converting videos when files are uploaded.
- Scheduled jobs: nightly reports, data clean-up and reminders.
- Stream and event processing: reacting to orders, payments, sensor readings and log events.
- Chatbots and webhooks: handling messages from chat platforms and payment providers.
- Back ends for single-page and mobile apps using managed authentication and databases.
17. Popular Serverless Platforms
| Platform | Provider | Notes |
|---|---|---|
| AWS Lambda | Amazon Web Services | The most widely used FaaS; deep integration with AWS services; supports Java |
| Azure Functions | Microsoft Azure | Strong Java and .NET support; integrates with Azure services |
| Cloud Run functions | Google Cloud | Formerly called Cloud Functions; event-driven functions on Google Cloud |
| Cloudflare Workers | Cloudflare | Runs code at the edge, close to users, with very fast starts |
| Knative / OpenFaaS | Open source | Serverless-style platforms you can run on your own Kubernetes cluster |
18. Serverless in System Design Interviews
Interviewers use serverless to test whether you understand trade-offs, not just definitions. Be ready to:
- Explain when you would choose functions over containers or servers, using traffic patterns and cost.
- Describe how you would handle cold starts for a latency-sensitive API.
- Show how functions keep state in managed databases, caches and storage.
- Explain idempotency, retries and dead-letter queues for event processing.
- Discuss how to stop thousands of parallel functions from overloading a database.
- Mention observability: structured logs, metrics and distributed tracing.
A good example answer: "For image uploads, I would trigger a function from the storage bucket, process the image asynchronously, store the result in object storage and record metadata in a managed database, with failed events going to a dead-letter queue."
19. Best Practices
- Design for statelessness and idempotency from day one.
- Initialise expensive objects once, outside the handler, and keep deployment packages small.
- Choose memory based on measurements; more memory often makes functions faster and sometimes cheaper overall.
- Set sensible timeouts, retries and dead-letter queues for every asynchronous trigger.
- Use infrastructure as code, such as AWS SAM, the Serverless Framework, Terraform or AWS CDK, so environments are repeatable.
- Log in a structured format and enable tracing to follow requests across functions.
- Keep secrets in a secrets manager, not in code or plain environment variables.
- Set budget alerts so a bug or traffic surge does not cause an unexpected bill.
20. Common Mistakes to Avoid
| Mistake | What goes wrong | Better approach |
|---|---|---|
| Storing state in function memory | Data is lost when the copy is recycled, and copies disagree | Keep state in a database, cache or storage |
| Creating clients inside the handler | Every call pays the setup cost and runs slower | Create clients once, outside the handler |
| Non-idempotent event handlers | Duplicate events create duplicate orders or emails | Use unique IDs and "save if absent" logic |
| One giant function doing everything | Slow cold starts, hard to test and deploy | Split into small, focused functions |
| Long chains of functions calling each other synchronously | You pay for waiting time, and failures cascade | Use queues, events or a workflow service |
| Unlimited concurrency against a small database | The database runs out of connections | Limit concurrency or use a connection proxy |
21. Summary
- Serverless lets you run code without managing servers; the provider handles infrastructure, scaling and availability.
- Functions run in response to events, scale automatically from zero, and are billed per request and execution time.
- FaaS runs your code, BaaS provides ready-made backend services, and most applications use both.
- Cold starts are the main performance trade-off; small packages, reused clients, provisioned concurrency and SnapStart reduce them.
- In Java, implement
RequestHandlerfor AWS Lambda, or use Spring Cloud Function for portable code. - Design functions to be small, stateless and idempotent, and connect them with events and queues.
- Serverless shines for event-driven, spiky and fast-moving workloads, but is not ideal for long-running jobs or constant heavy load.
Understanding both the strengths and the trade-offs of serverless helps you choose the right tool for each part of a system. Open the FAQ tab for answers to common questions about serverless architecture.
Frequently Asked Questions (FAQ)
Q1. What is serverless architecture in simple words?
Serverless architecture is a way to run code in the cloud without managing any servers. You write small functions, and the cloud provider runs them whenever an event happens, such as an HTTP request or a file upload.
The provider handles servers, scaling and availability, and you pay only while your code is running.
Q2. Why is it called serverless if servers still exist?
Servers are still there, but you never see or manage them. You do not choose machine sizes, install operating systems, apply patches or decide how many servers to run. From the developer's point of view, the servers have disappeared, which is where the name comes from.
Q3. What is the difference between FaaS and BaaS?
- FaaS (Function as a Service): you write code that the platform runs on events, such as AWS Lambda or Azure Functions.
- BaaS (Backend as a Service): ready-made backend features you use without writing server code, such as authentication, databases and file storage, for example Firebase or Amazon Cognito.
Most serverless applications combine the two.
Q4. What is a cold start?
A cold start is the extra delay when a function runs on a brand-new copy. The provider must create an environment, start the runtime, load your code and run its initialisation before handling the request.
Later requests reuse the ready copy (a warm start) and are much faster. Cold starts are usually a few hundred milliseconds, and can be longer for large Java applications.
Q5. How can I reduce cold starts, especially in Java?
- Keep deployment packages small and initialisation light.
- Create clients and connections once, outside the handler.
- Increase memory, which also increases CPU.
- Use provisioned concurrency to keep copies ready for latency-sensitive endpoints.
- On AWS Lambda, enable SnapStart for Java, or build a GraalVM native image.
Q6. How does serverless pricing work?
Most FaaS platforms charge for:
- Requests: a small fee per million invocations.
- Compute time: execution time multiplied by the memory assigned, measured in GB-seconds.
When nothing runs, you pay nothing for compute. Many providers include a monthly free allowance; check the current price list, as rates vary by provider and region.
Q7. Is serverless always cheaper than servers?
No. Serverless is usually cheaper for spiky, unpredictable or low traffic, because you never pay for idle servers. For workloads with constant, heavy traffic all day, always-on servers or containers, especially reserved ones, can cost less.
Compare both options using your real traffic pattern.
Q8. How long can a serverless function run?
Functions have a maximum run time per invocation. On AWS Lambda it is 15 minutes; other platforms have their own limits, and HTTP requests through an API gateway often have shorter time limits.
For longer work, split it into smaller steps coordinated by a workflow service, or use containers or batch services.
Q9. How do serverless functions handle state?
Functions should be stateless: a copy can be created or removed at any time, so data kept in memory may disappear. Store state in external services instead:
- Databases such as DynamoDB or a relational database.
- Caches such as Redis.
- Object storage such as Amazon S3.
- Workflow services for multi-step processes.
Q10. Can I write serverless functions in Java?
Yes. AWS Lambda, Azure Functions and Google Cloud all support Java. On AWS Lambda, you implement the RequestHandler interface, or use Spring Cloud Function to write portable functions as plain java.util.function.Function beans.
For Java, pay attention to cold starts: keep dependencies lean and consider SnapStart or GraalVM native images.
Q11. What is Spring Cloud Function?
Spring Cloud Function is a Spring project that lets you write business logic as standard Java functional interfaces, Function, Supplier and Consumer, declared as Spring beans.
The same code can run as a web endpoint locally, or on AWS Lambda and Azure Functions using adapters, which reduces lock-in to one provider's API.
Q12. Can serverless be used with microservices?
Yes. Each microservice can be built from one or more functions behind an API gateway, communicating through events and queues. Serverless also fits alongside container-based microservices, for example for background processing or scheduled tasks.
Keep each service's functions and data together, and avoid long chains of synchronous calls between functions.
Q13. What is vendor lock-in in serverless?
Vendor lock-in means your application depends heavily on one provider's services, such as its triggers, permissions model, databases and queues, so moving to another provider would take significant work.
You can reduce it by keeping business logic in plain code, using portable frameworks like Spring Cloud Function, and isolating provider-specific code behind interfaces.
Q14. Why should serverless functions be idempotent?
Event sources often guarantee delivery at least once, so the same event can occasionally arrive twice, for example after a retry. An idempotent function produces the same result no matter how many times it runs for the same event.
Use unique event or order IDs and "save if absent" logic so duplicates do not create duplicate orders, payments or emails.
Q15. How do I connect a serverless function to a database?
Thousands of function copies can open thousands of database connections at once, which can overwhelm a traditional database. Common solutions are:
- Use a serverless-friendly database such as DynamoDB, which works over HTTP.
- Use a connection proxy, such as Amazon RDS Proxy, in front of relational databases.
- Limit the function's concurrency.
- Create the connection once, outside the handler, so warm calls reuse it.
Q16. How do I test and debug serverless applications?
- Unit test business logic as plain Java classes, separate from the handler.
- Run functions locally with tools such as AWS SAM CLI or Spring Cloud Function's web mode.
- Run integration tests in a real cloud test environment, since managed services behave differently locally.
- Use structured logs and distributed tracing, such as AWS X-Ray, to follow requests across functions.
Q17. What is the difference between serverless and containers?
With containers, you package your application with its runtime and run it on a cluster or managed container service; it usually runs continuously and you manage scaling settings and images.
With serverless functions, you upload only code, it runs per event, scales to zero automatically and is billed per request. Services such as AWS Fargate and Google Cloud Run sit in between, running containers without managing servers.
Q18. What are common use cases for serverless?
- REST and GraphQL APIs for web and mobile apps.
- Processing uploaded images, documents and videos.
- Scheduled jobs such as nightly reports and clean-ups.
- Event and stream processing for orders, payments and IoT data.
- Webhooks and chatbots.
- Back ends for prototypes and new products.
Q19. When should I not use serverless?
- Long-running jobs that exceed function time limits.
- Constant, very high traffic where always-on servers are cheaper.
- Applications needing consistently very low latency where cold starts are unacceptable.
- Workloads needing special hardware, OS-level control or long-lived connections.
- Systems that must run unchanged across several clouds or on-premises.
Q20. How is serverless asked about in system design interviews?
Interviewers look for an understanding of trade-offs. Be ready to explain:
- When you would choose functions over containers or servers, based on traffic and cost.
- How you would handle cold starts, state and database connections.
- How retries, idempotency and dead-letter queues keep event processing reliable.
- How you would monitor and trace requests across many functions.