Pollito Dev
February 12, 2025

I built the same app thrice

Posted on February 12, 2025  •  8 minutes  • 1641 words  • Other languages:  Español

Inspiration

This blog is heavily inspired by Theo’s “I built the same app with 5 different stacks” video.

So I decided to do my take on it, but with two languages I already know well (Java and Groovy ) plus one new language I wanted to try for a long time: Kotlin .

Here are the code for the repos:

Understanding the application

I made the typical “Roundest Pokémon” programming exercise:

I added a twist:

Here’s the code for the Next.js frontend .

Quick metrics

This blog is mostly a comparison of backends. Spoiler alert, in terms of performance in this small sample project, they are the same.

Let’s do some comparison of the codebases:

The following tables were generated using cloc .

Backend application Groovy

Language files blank comment code
Groovy 22 174 1 727
YAML 3 0 0 296
SQL 1 1 2 157
Bourne Shell 1 28 118 106
Gradle 2 12 0 96
DOS Batch 1 21 2 71
Markdown 1 10 0 47
Dockerfile 1 1 2 7
Properties 1 0 0 7
SUM: 33 247 125 1514

Backend application Java

Language files blank comment code
Java 22 131 2 660
YAML 3 0 0 296
SQL 1 1 2 157
Gradle 2 12 0 108
Bourne Shell 1 28 118 106
DOS Batch 1 21 2 71
Markdown 1 9 0 45
Dockerfile 1 1 2 7
Properties 1 0 0 7
SUM: 33 203 126 1457

Backend application Kotlin

Language files blank comment code
Kotlin 24 134 4 589
YAML 3 0 0 301
Gradle 2 25 0 167
SQL 1 1 2 157
Bourne Shell 1 28 118 106
DOS Batch 1 21 2 71
Markdown 1 9 0 44
Dockerfile 1 1 2 8
Properties 1 0 0 7
SUM: 35 219 128 1450

When it comes to deployment times, also there’s nothing remarkable.

I have all the backends with the same very conservative resource limits: resource-limits.png

On idle they have acceptable CPU and memory usage. All of them present:

There’s no The Good, the Bad and the Ugly

All three options are totally valid for a serious big project, and they would fall into “The Good”.

I would say a better phrase would be “The Good, the First Love, and the Disappointment”. Let’s go one by one.

The Good: Java

Let’s start by inserting obvious public static void String main args joke here.

Fun fact, public static void String main args is no longer needed since Java 21 .

In one word, Java is reliable:

Everything just worked, probably because Java is what I’ve been doing for 8 hours a day, 5 days a week, for more than 2 years by now.

Java is not glamorous, but comfortable.

honest-work-meme-c7034f8bd7b11467e1bfbe14b87a5f6a14a5274b.jpg

The First Love: Groovy

My journey with Groovy began back in 2021. I remember in the job interview I was only asked two things:

It was a simpler time.

Without realizing, I had joined a project that was built using Grails , a very niche monolith framework that uses Groovy as its primary language.

I quickly fell in love with its expressive syntax and the way it aimed to make Java better by cutting down on boilerplate and embracing a more dynamic style.

Yet Groovy remains the indie artist of JVM languages: beloved by Gradle buildscript writers and the few Grails developers that may exist out there, but never quite achieving Scala’s academic prestige or Kotlin’s JetBrains-backed fame.

Groovy relaxed typing

Groovy relaxed typing is a double edge sword.

During the writing of the Groovy version, I had a CORS issue. My first immediate suspect was a bad configured application.yml (as I read the allowed origins from that file), but the solution was this:

Screenshot2025-02-11190416.png

I had as String probably from an IntelliJ suggestion or ChatGPT copy-paste, but that was enough to break CORS in the application. These kind of mistakes simply don’t happen in Java.

Writing tests with Spock

You can use JUnit in a Groovy based project, but would be a waste to not use Spock (is like going to Madrid and not eating a tortilla).

I always found Spock syntax more readable, personal preference though. Here you have a snippet of code in Java Junit, and Groovy Spock, both testing the find Pokémon by id functionality

Java JUnit

@Test
void whenFindByIdThenReturnPokemon() {
    when(pokemonRepository.findById(anyLong())).thenReturn(Optional.of(mock(Pokemon.class)));
    assertNotNull(pokemonService.findById(1L));
}

Groovy Spock

def "when findById then return Pokemon"(){
    given: "a mocked repository behaviour"
    pokemonRepository.findById(_ as Long) >> Optional.of(new Pokemon())

    when: "finding a pokemon"
    def result = pokemonService.findById(1L)

    then: "result is not null"
    result != null
}

I would use Groovy again given the chance

Not because it’s objectively superior, but because maintaining code should feel like coming home. even if home has some leaky type checking and mysterious NoSuchMethodError ghosts in the closet. I guess I miss being part of a project I really care, and Groovy reminds me of those days.

The Disappointment: Kotlin

Disclaimer: This was my first time starting a Kotlin project solo, so maybe my bad experience is due to skill issue. skill-issue-skill-3427506110.gif

OpenAPI Generator didn’t work out of the box

I’m a big fan of OpenAPI Generator, and I don’t ever want to write a DTO ever again. Using the OpenAPI Generator Gradle Plugin was very simple in Java and Groovy, but in Kotlin I had two issues:

Those extra steps felt like a step backward in terms of efficiency. You can say “Bro just write the DTOs yourself”, to which I answer “I didn’t have to do that in Java and Groovy, why do I have to write them here?”

Handling java time in tests

You can also use JUnit in a Kotlin based project, but would be a waste to not give a try to MockK . It is quite close to JUnit syntax.

The problem arrived when it was unable to mock java.time.Instant and java.time.format.DateTimeFormatter.

If you are curious how MockK looks like, here’s a test on the find Pokémon by id functionality:

@Test
fun `when findById then return Pokemon`() {
    val pokemon = Pokemon(name = "Bulbasaur", spriteUrl = "url")
    every { pokemonRepository.findById(any<Long>()) } returns Optional.of(pokemon)
    
    assertNotNull(pokemonService.findById(1L))
}

It was not bad

But it was the little things that didn’t convince me. Maybe my expectations for Kotlin were a bit too high, or perhaps I simply took a few wrong turns along the way. Despite these frustrations, I’m not closing the door on Kotlin entirely.

Conclusion

Hey, check me out!

You can find me here