Een serverless architectuur bouwen met Java en Quarkus

Mijn talk op de AWS User Group UK
Op de AWS User Group UK sprak ik onlangs over hoe we bij Ennovative Solutions een serverless SaaS-applicatie bouwen in Java, en hoe Quarkus ons helpt om de cold starts van onze Lambda-functies onder controle te houden.
In deze post zet ik de belangrijkste punten op een rij. De volledige opname kun je hier bekijken: Creating a serverless architecture using Java & Quarkus.
Update november 2026: onderaan deze post lees je wat er sindsdien veranderd is en wat ik vandaag anders zou doen.
De context: een SaaS-product, volledig serverless
Voor een SaaS-product is serverless een logische keuze. Je betaalt per request in plaats van per draaiende server, elke klant schaalt automatisch mee, en er is geen infrastructuur om te patchen of te monitoren op OS-niveau.
Onze architectuur steunt op de klassieke AWS-bouwstenen: API Gateway als voordeur, Lambda-functies voor de businesslogica en managed services voor opslag en events. Elke functie doet één ding en kan apart gedeployed en geschaald worden.
De keuze voor Java ligt voor de hand: het is de taal waarin ons team het sterkst staat, met een volwassen ecosysteem en tooling. Alleen heeft Java op Lambda één bekende zwakke plek.
Concreet gaat het om PURQR, ons platform waarmee retailers hun showroom beheren via QR-codes: in-store analytics, afspraken en een gepersonaliseerde opvolging na het bezoek. De backend bestaat uit zeven Quarkus-microservices, de meeste HTTP-gebaseerd achter API Gateway, met SQS voor asynchrone e-mails en DynamoDB voor opslag. Ze draaien in productie sinds oktober 2019.
Het probleem: Java en cold starts
Een cold start is de eerste aanroep van een Lambda-functie waarvoor nog geen warme uitvoeringsomgeving klaarstaat. AWS moet dan een nieuwe omgeving opzetten, je code laden en de runtime initialiseren.
Voor Java is dat traditioneel duur. De JVM moet opstarten, klassen laden en verifiëren, en frameworks zoals Spring doen bij het opstarten veel werk via reflection en classpath scanning. Een cold start van meerdere seconden is geen uitzondering.
Voor een SaaS-product is dat merkbaar: de gebruiker die net die koude functie raakt, wacht. En bij een architectuur met veel kleine functies gebeurt dat vaker dan je zou denken, zeker bij weinig verkeer of na een nieuwe deploy.
Onze aanpak: Quarkus en native images
Quarkus pakt het probleem aan de bron aan: zoveel mogelijk werk gebeurt tijdens de build in plaats van bij het opstarten. Configuratie, dependency injection en annotaties worden vooraf verwerkt, waardoor er bij een cold start veel minder te doen valt.
De echte winst komt van de native build. Met GraalVM compileert Quarkus je applicatie vooraf naar een native binary. Die draait zonder JVM, start in milliseconden en gebruikt beduidend minder geheugen, wat op Lambda ook rechtstreeks in de kosten scheelt.
Zo ziet de opzet eruit:
- De
quarkus-amazon-lambdaextensie zorgt voor de koppeling tussen je handler en de Lambda-runtime. - De native build draait in een container, zodat de binary compatibel is met de Linux-omgeving van Lambda.
- De binary wordt als custom runtime (
provided) gedeployed, met eenbootstrap-bestand als startpunt.
Dat heeft een prijs. Native builds duren minuten in plaats van seconden, bibliotheken die op reflection steunen hebben extra configuratie nodig, en niet elke library werkt zonder meer in een native image. Debuggen gebeurt daarom best in JVM-modus, met de native build als stap in de pipeline.
Tot slot
Quarkus maakt Java een volwaardige keuze voor serverless. De native build vraagt wat extra werk in je pipeline, maar de winst in opstarttijd en geheugengebruik is het meer dan waard.
Ben je zelf een serverless architectuur in Java aan het bouwen, of twijfel je tussen native en SnapStart? Bij Ennovative Solutions denken we graag mee. Neem gerust contact op.
Update november 2026: wat er sindsdien veranderd is
De grootste verandering: AWS heeft het cold-startprobleem voor Java zelf aangepakt. Een native build is daardoor niet langer de enige manier om snelle Java-functies te krijgen.
| Datum | Wat veranderde | Waarom het telt |
|---|---|---|
| 2026 | provided.al2 uitgefaseerd, opvolger is provided.al2023 (ondersteund tot juni 2029) | Native Quarkus-functies op de oude custom runtime moeten migreren |
| november 2025 | Java 25-runtime op Lambda, met AOT-caches in plaats van Class Data Sharing | Snellere cold starts op de gewone JVM, ook zonder SnapStart |
| november 2022 | Lambda SnapStart gelanceerd voor Java 11 | Cold starts tot tien keer sneller, zonder native build |
SnapStart in het kort
Bij SnapStart initialiseert Lambda je functie bij het publiceren van een versie, neemt een snapshot van die opgestarte omgeving en hervat nieuwe instanties vanuit die snapshot. Het werkt vandaag voor Java 11 en hoger, Python 3.12+ en .NET 8+, en voor Java zijn er geen extra kosten aan verbonden.
Er zijn wel randvoorwaarden. SnapStart werkt alleen op gepubliceerde versies, niet op $LATEST, en niet op custom runtimes of container images. Een native Quarkus-binary en SnapStart sluiten elkaar dus uit.
Quarkus speelt erop in
Quarkus 3 heeft SnapStart-optimalisaties ingebouwd in de Lambda-extensie, standaard actief. Klassen worden vooraf geladen voor de snapshot, en via de CRaC-API kun je clients zoals de DynamoDB-client al initialiseren vóór de snapshot (priming). Dat haalt nog een flinke hap uit de eerste aanroep.
Update: wat ik vandaag anders zou doen
Vandaag zou ik starten met Quarkus op de beheerde Java-runtime met SnapStart, en pas naar een native build grijpen als metingen dat rechtvaardigen. Quarkus blijft een uitstekende keuze; wat verandert is de standaard deploymentvorm.
| Aanpak | Cold start | Build en onderhoud | Wanneer kiezen |
|---|---|---|---|
| Quarkus + JVM + SnapStart | Kort, zeker met priming | Gewone JVM-build, beheerde runtime die AWS patcht | De standaard voor de meeste API's en achtergrondtaken |
Quarkus native (provided.al2023) | Het kortst, plus laag geheugengebruik | Trage builds, reflection-configuratie, zelf de runtime bijhouden | Hoge volumes waar geheugen en kosten per aanroep tellen |
| Quarkus + JVM zonder SnapStart | Het langst, maar beter dankzij Java 25 | Het eenvoudigst | Asynchrone functies waar latency niet telt |
Drie lessen die ik meeneem:
- Meet eerst. Cold starts raken vaak maar een klein deel van de aanroepen. Kijk naar je eigen cijfers voor je complexiteit toevoegt.
- Kies de beheerde runtime waar het kan. AWS patcht de JVM en het OS voor je; bij een custom runtime ben je daar zelf verantwoordelijk voor, zoals de migratie van
provided.al2liet zien. - Ontwerp voor snapshots. Unieke waarden, netwerkverbindingen en credentials die je tijdens de initialisatie aanmaakt, kunnen na een restore hergebruikt worden. Gebruik de CRaC-hooks om ze te vernieuwen.
Bronnen
- Creating a serverless architecture using Java & Quarkus, AWS User Group UK, september 2021
- Improving startup performance with Lambda SnapStart, AWS-documentatie
- AWS Lambda now supports Java 25, AWS Compute Blog
- When to use Lambda's OS-only runtimes, AWS-documentatie
- AWS Lambda SnapStart Configuration, Quarkus-gids
- AWS Introduces Lambda SnapStart for Java, ADTmag