- 9 minutes read

That's the wrong question. You shouldn't use either of them. Granted, that's probably not the answer you expected, so let me explain. Like so often, I don't insist on being right. Quite the contrary, I'm looking forward to heated and fruitful discussions. I want to make you think. But I guess I can convince you!

Everybody's using Spring Boot!

At first glance, it's simple and obvious. Everybody and their Grandma are using Spring Boot, so using it in an AWS Lambda can't be wrong. If you've ever tried to write a business application without framework support, you also know why. Being the curious guy I am, I've tried writing a bare-bone business application several times. Bad idea. Don't do that at home! All those fancy abstractions are there for a good reason. They are the distilled wisdom of generations of programmers. Plus, a dedicated Spring module called Spring Cloud Function supports AWS Lambdas and Azure Functions. When I researched GraalVM and Quarkus some time ago, I was fascinated by how fast Lambdas start with native images and GraalVM. At the time, it was evident to me that every decent Java application needs either Quarkus, JavaEE, Spring Boot, or one of the rising microframeworks.

Here's the catch. We're talking about AWS Lambdas. These cuties are meant to be small. They suffer from a remarkable short life expectancy. At the time of writing, no Lambda gets older than 15 minutes. In the past, it was worse: AWS used to kill them after 5 minutes.

So Lambdas are an ideal match for short-running, isolated tasks. That's a significant mind shift.

Spring Boot has an entirely different use case. It has been written with huge, long-running applications in mind. It adds structures, best practices, and a lot of concepts, all for the sake of keeping up with an ever-growing application maintained by dozens of developers. You can use Spring Boot for smaller projects, but many of its ideas come from large-scale projects.

So simple tasks it is!

An AWS Lambda is simply a function. Nothing less and nothing more. It receives data, processes it, and returns process data. It's not an application. Usually, it's just a tiny part of an application.

I guess you can split most Spring Boot applications into several Lambdas. Every REST controller becomes an individual Lambda. More to the point, every REST endpoint becomes a Lambda. You don't have a controller class collecting multiple @Get and @Post methods. Instead, each of these methods becomes a Lambda of its own.

So we've got our first mind shift. If you're implementing Lambdas, you don't need a controller layer because AWS is the controller layer. You get rid of a whole layer of complexity. Or rather, you don't - you've merely shifted it from your application code to your infrastructure code.

What does the average Lambda do?

So it's no surprise most Lambdas I've seen so far have a fairly limited scope: they receive and process data. Maybe they also return data. To do so, they access databases and S3 buckets, and sometimes they call other Lambdas or REST services. In a few cases, they do a fan-out or are called by a step function.

If you've got such a Lambda, I wonder what Spring Boot can do for you. Or Quarkus. Or Helionaut. Maybe they simplify database access, but neither Spring Boot nor Quarkus implements the database layer themselves. They're using a third-party framework like Hibernate. Using Hibernate to deal with your relational database makes a lot of sense. It's just that you don't need Spring for that.

Another benefit of Spring is bean validation - but again, you can use Hibernate Validator. You don't need the entire Spring engine.

Dealing with JSON

Here's the killer feature of Spring Boot. It makes dealing with JSON extremely simple.

While that's true, you can also use the underlying framework because Jackson has a straightforward and simple API.

However, why do you need a JSON library in the first place? Other programming languages allow you to deal with JSON much more simply. So maybe Java is the wrong choice?

I do think so because of two reasons. You already know one of them. JSON objects are native objects in JavaScript, so it's effortless to deal with JSON in TypeScript and JavaScript. Give it a try - I guess you'll never want to return to Java. It's also remarkably simple to use JSON objects in Python. C# insists on using classes, but apart from that, it ships with a powerful JSON library out-of-the-box. Java is the only language available for Lambdas forcing you to use a library. There's the reason why many developers consider Java an Assembly language. It's rock-solid and fast but lacks the ease of use of younger programming languages.

No editor in the AWS console

The other reason is that the AWS console doesn't support editing Java files. The editor is a handy feature, allowing you to skip time-consuming deployment steps for many simple tests. You could argue that cutting corners is a bad idea, and usually, it is, but most developers love cutting that particular corner. Unfortunately, you can't edit a Java application in the AWS console. That's not surprising because you upload compiled code - usually *.class files or even binaries if you're using native images.

What about SnapStart?

I'm deeply impressed by the new SnapStart feature and even more impressed by how much thought the Quarkus project put into supporting SnapStart. In a nutshell, SnapStart allows you to deploy a pre-warmed Java Lambda, so your Java application is immediately available. It doesn't have to initialize Spring Boot because the snapshot is taken after initializing Spring Boot and a large part of your application.

Awesome! But dropping Spring Boot has a more significant effect. SnapStart solves a problem you don't have without Spring Boot. Well, mostly - even without Spring Boot, it's a great tool. I've run tests with a Lambda that's as tiny as can be, and SnapStart shaves off a couple of milliseconds. I suppose it gets better the more classes your Lambda uses, so it's the way to go.

By the way, I wonder whether we should implement tree shaking in Java. When I followed Baeldung's recipe to implement a Lambda, I ended up with a whopping megabyte for a simple hello world program. Dropping the library providing the SQS interfaces, com.amazonaws:aws-lambda-java-events, reduced the size of the jar file to 14 KB. Luckily, these additional 524 classes aren't loaded by the JVM, so the performance penalty is very small. I couldn't even measure it.

Adopting Lambdas requires a mind-shift

I've been working with Lambdas for several years now, and the one thing I like about them is they're simple. However, if experienced Spring developers have implemented them, they're usually the same convoluted mess the average Java backend application is. It takes a long time to wrap my head around it. Maybe you're more clever than me, but then, the art of enterprise-level programming is about writing boring code everybody understands at first glance. Piling up too many abstraction layers is almost as bad as writing spaghetti code.

Thing is, Spring has been designing with a different use case in mind. The natural habitat of Spring Boot is applications running for months without interruption, serving millions of users, and supporting your operations department (or the unlucky DevOps engineer trying to do both programming and operations).

Lambdas are entirely different. Your Lambda finishes after five seconds. AWS cares about scaling and operating your service for months. It serves precisely one user. Granted, millions of users can call your Lambda, but AWS silently makes it possible. From a programmer's point of view, there's just one user. There's no state in Lambdas. Each time it starts fresh. And, of course, you don't need to care about monitoring. Cloudwatch already covers that. You don't even need a logger framework. A simple System.out.println() does the trick just as well, even if Sonarqube keeps telling you otherwise.

Hey, you don't even need a logger!

If you insist on using a logger, ask yourself why. Does it have any benefit over using System.err.println() and System.out.println()? The benefit is evident in a typical Spring Boot application consisting of thousands of packages, tens of thousands of classes, and a zillion lines. Adding a timestamp and reporting the exact line of the source code is a huge benefit. Not to mention MDC, allowing you to add context information. But in a Lambda? Cloudwatch automagically adds the timestamp. You don't need to know the precise method and line number if your Lambda is cute and tiny. It's easy to find a particular log message if the entire application has merely one or two dozen lines of logging code. You can even argue that AWS cares about reporting the location because that's the name of the Lambda.

The only benefit of using Log4J and its friends is MDCs. That can be extremely useful. There's nothing wrong with using a logger framework. You just should ask yourself if it'll ever pay off. Logger frameworks come with a price tag:

  • You need to configure it.
  • They add to your memory footprint.
  • They reduce performance.
  • Of course, they've got a cold start penalty.

By the way, you don't have to take my word for it. There's an authority few people doubt: The Twelve-Factor App manifesto suggests logging to STDOUT.

Wrapping it up

Lambdas are simply functions, and that's just great. You can eliminate most of your enterprise-grade overhead, including complex frameworks like Spring or Quarkus. Try as I might, I can't imagine how any of my Lambda benefits dependency injection. DI is a tool required to manage large code bases - but if your Lambda is a large code base, you've done it wrong. It's better to run it in Fargate or EC2.

Of course, I don't suggest re-implementing the wheel. Even Lambdas benefit from frameworks. If you're using a relational database, Hibernate or JPA are an excellent match for your Lambda. All I'm saying is you should think twice before adding a framework. "We're using Spring Boot everywhere else" is a lousy reason.

Lambdas are the most fun if they're cute and simple, so you should try hard to reduce complexity. Adding Spring Boot reduces complexity in long-running server applications but increases complexity in Lambdas.

Foreign feathers

The Twelve-Factor App manifesto on logs

Corey Cleary on logging best practices

Sebastian Hesse's thoughts on using Spring Boot with Lambdas