Een serverless architectuur bouwen met Java en Quarkus

16/09/2021
Bijgewerkt op 06/11/2026
Dennis Baerten
Titeldia van de talk Lambda with Java & Quarkus
Creating a serverless architecture using Java & Quarkus, AWS User Group UK. De video komt van YouTube en laadt pas als je op afspelen klikt.

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:

  1. De quarkus-amazon-lambda extensie zorgt voor de koppeling tussen je handler en de Lambda-runtime.
  2. De native build draait in een container, zodat de binary compatibel is met de Linux-omgeving van Lambda.
  3. De binary wordt als custom runtime (provided) gedeployed, met een bootstrap-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.

DatumWat veranderdeWaarom het telt
2026provided.al2 uitgefaseerd, opvolger is provided.al2023 (ondersteund tot juni 2029)Native Quarkus-functies op de oude custom runtime moeten migreren
november 2025Java 25-runtime op Lambda, met AOT-caches in plaats van Class Data SharingSnellere cold starts op de gewone JVM, ook zonder SnapStart
november 2022Lambda SnapStart gelanceerd voor Java 11Cold 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.

AanpakCold startBuild en onderhoudWanneer kiezen
Quarkus + JVM + SnapStartKort, zeker met primingGewone JVM-build, beheerde runtime die AWS patchtDe standaard voor de meeste API's en achtergrondtaken
Quarkus native (provided.al2023)Het kortst, plus laag geheugengebruikTrage builds, reflection-configuratie, zelf de runtime bijhoudenHoge volumes waar geheugen en kosten per aanroep tellen
Quarkus + JVM zonder SnapStartHet langst, maar beter dankzij Java 25Het eenvoudigstAsynchrone 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.al2 liet 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