Configuring Gradle and Spring Boot with H2 for local development

Posted on

If you’re anything like me, you HATE long compilation times. Frequently, we find ourselves in an absurd situation where we discover a new programming language that we love so much, but it also has slower compilation times when compared to its predecessor. Prime examples are Kotlin and TypeScript. It also seems that compilation times are just getting slower, version by version. It’s a sad, sad world.

As the famous XKCD comic jokes:

XKCD Compiling

If you’re also like me, you absolutely LOVE good tooling. Good tooling has the potential to make our lives easier, enable us to lose less time waiting or doing empty work. HotSwap for JVM or TypeScript Language Server comes to mind.



The problem with Spring Boot and databases

There’s no such thing as a free lunch. Sure, Spring Boot has awesome tooling, great development experience. But, it is famous for its NOTORIOUSLY bad start-up time. Some Spring Boot apps take longer to start than my entire OS. It’s just hilarious.

Oftentimes, a major contributor to the start-up time is initializing the connection to the database. If you’re using Flyway or Liquibase, it is even worse, as it takes time for them to validate your database, or change it if needed.

To waste some more of your precious time, people usually configure their local development environment to use an external database, so you also need to start this as well. Sure, it’s good to use the same database for your local development as you do in production, but it’s so unnecessary. Configure your integration tests to use that database to check for any potential errors.

Need to modify your database schema? Good luck restarting your DB instance each and every time you make a change.



Using H2 in-memory DB for local development with Gradle

The solution to the mentioned problem is to just use an in-memory database for local development. It’s going to make your life so much easier.

Let’s see how we can configure Gradle to use H2, but only for local development.

As an example, let’s say that we have an application with dependencies that look like this, in your build.gradle, with PostgreSQL as your production database:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Enter fullscreen mode

Exit fullscreen mode

You can just add the H2 dependency to this section, but that would mean that in production, your app is going to have the H2 included in it as well. Let’s not do that. It’s filthy.

By default, the java plugin in build.gradle declares two source sets, main and test.

What we’re going to do is add a new sourceSet and configuration just for local development in build.gradle. We can call it localH2. You will need to add this piece of code before declaring your dependencies.

configurations {
    localH2Implementation.extendsFrom implementation
    localH2RuntimeOnly.extendsFrom runtimeOnly
}

sourceSets {
    localH2 {
        compileClasspath += sourceSets.main.output
        runtimeClasspath += sourceSets.main.output
    }
}
Enter fullscreen mode

Exit fullscreen mode

This configuration enables us to define dependencies that are specific only to our local environment. But, it includes the whole main source set, so you’ll have all the dependencies that you included normally.

We can now add the H2 dependency, like this:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    localH2RuntimeOnly 'com.h2database:h2'
}
Enter fullscreen mode

Exit fullscreen mode

We’re almost there, but now, in our localH2 source set, we also have the PostgreSQL driver included. To remove that dependency, we can use this block of code:

configurations.localH2Implementation {
    exclude group: 'org.postgresql', module: 'postgresql'
}
Enter fullscreen mode

Exit fullscreen mode

Ah, now everything is so clean.

OCD



Running a Spring Boot application with the local configuration

We now have two classpaths separated, for the production code and for our local development. But, we haven’t explored how we can run this local configuration from your machine. If you just run the application through your IDE or with ./gradlew bootRun, it’s just going to use the production classpath.

If you’re using IntelliJ, you’ll have to make a small change in your Run Configuration to use the new classpath.

IntelliJ Run Configuration

If you’re running your app with ./gradlew bootRun, don’t worry, it’s a pretty simple change as well.

You can add a new Gradle task to your build.gradle to run it with the new classpath, like so:

task localH2(type: org.springframework.boot.gradle.tasks.run.BootRun){
    mainClass = "gradle.springboot.h2.local.example.ExampleApplication"
    classpath = sourceSets.localH2.runtimeClasspath
}
Enter fullscreen mode

Exit fullscreen mode

The mainClass property should contain a reference to your main Java class. You can now run your app by calling ./gradlew localH2. Isn’t it nice?

Even better, having H2 in the classpath configures your DataSource automatically, so there’s no additional configuration to be added.



Important note

If you had your DataSource configured in your application.yml / application.properties, you should pull that config out into a new profile (let’s say, postgres?), so that it’s not loaded by default.



Final thoughts

Get ready to indulge in the newfound time that you didn’t even know you had. Too many times, have we, as developers, had to wait for that slow Spring Boot app to start.

There are at least some ways to make our lives easier and isn’t that the whole point of DevOps and the rapid development culture of today?

Be safe!

The accompanying code can be found at the Github Repository.

Leave a Reply

Your email address will not be published. Required fields are marked *