1. Introduction

MariaDB is an alternative database engine to MySQL, written by the original developers of MySQL and compatible as a drop-in replacement. In this article, we’re going to explore the MariaDB4j library that allows us to treat MariaDB as if it was an embedded database within our Java application.

2. How Does It Work?

MariaDB4j works by having packaged ready-to-run native versions of the MariaDB database engine. It does this with a number of different dependencies. The majority of the functionality exists in the ch.vorburger.mariaDB4j:mariaDB4j-core artifact. There are then additional artifacts that each contain a copy of the MariaDB database engine that’s ready for use. Versions of this are available for Windows, Linux, and macOS.

When using the MariaDB4j library, it will automatically determine the current operating system and then attempt to extract the MariaDB database engine into a temporary working directory. This will then be executed as any other application. Once running, we can connect to it exactly the same as if it were a full standalone database.

The MariaDB4j library is then responsible for extracting, configuring, starting and stopping the database engine.

This is an alternative to solutions such as Testcontainers, which work by starting the database as a Docker container. This means that we’re able to use this database even in environments where Docker either isn’t available or may be less desirable to use.

3. Command Line Use

The first way we can use MariaDB4j is as a command-line wrapper. This only supports Windows and Linux but provides an easy way to run our database without needing a full install.

In order to use this, we need to download the uber mariaDB4j-app.jar JAR file that’s provided. We can find this in Maven Central. The latest available version is 3.1.0 at the time of writing. This contains the MariaDB4j core code and also database engines for both Windows and Linux.

Once we download this, we can run the JAR file like any other Java application:

$ java -jar mariaDB4j-app-3.1.0.jar
.....
2024-11-08T07:38:47.867Z  INFO 40 --- [pool-9-thread-2] ch.vorburger.exec.ManagedProcess         : mariadbd: 2024-11-08  7:38:47 0 [Note] /tmp/MariaDB4j/base/bin/mariadbd: ready for connections.
2024-11-08T07:38:47.868Z  INFO 40 --- [pool-9-thread-2] ch.vorburger.exec.ManagedProcess         : mariadbd: Version: '10.11.5-MariaDB'  socket: '/tmp/MariaDB4j.3307.sock'  port: 3307  MariaDB Server
2024-11-08T07:38:47.924Z  INFO 40 --- [           main] ch.vorburger.mariadb4j.DB                : Database startup complete.
2024-11-08T07:38:48.454Z  INFO 40 --- [           main] c.v.m.s.boot.MariaDB4jApplication        : Started MariaDB4jApplication in 6.589 seconds (process running for 8.281)

Hit Enter to quit...

Once this has finished loading, our database is ready for use.

If needed, we can also customize the execution with some system properties. The most useful of these is mariaDB4j.port, which will allow us to control the port number that the database listens on. We can also use mariaDB4j.baseDir to control where the MariaDB installation will be unpacked and mariaDB4j.dataDir to control where the data files will be stored:

$ java -DmariaDB4j.port=13306 -DmariaDB4j.baseDir=/tmp/db -DmariaDB4j.dataDir=/tmp/data -jar mariaDB4j-app-3.1.0.jar

4. Using From Java

More likely, we want to be able to run MariaDB automatically from within our application.

In order to do this, we need to add the main MariaDB4j dependency to our application. If we’re using Maven, we can include this dependency in our pom.xml file:

<dependency>
    <groupId>ch.vorburger.mariaDB4j</groupId>
    <artifactId>mariaDB4j</artifactId>
    <version>3.1.0</version>
</dependency>

At this point, we’re ready to start using it in our application. By default, this will include the dependencies needed for Windows and Linux, running the default supported version of MariaDB – 10.11.5. There’s also a macOS dependency that can be added. However, this doesn’t support the latest version yet and will need more work to run.

Once we install the dependency, we can start using it. The simplest way is to call DB.newEmbeddedDB(), providing a port number to listen on:

DB db = DB.newEmbeddedDB(0);

Using 0 here will cause the system to find an unused port. Using any real port number will cause it to use that instead.

Once we’ve done this we can start the database with db.start() and stop it with db.stop():

try {
    db.start();
} finally {
    db.stop();
}

At this point, our database is running and ready for use.

4.1. Configuring Our Database

Instead of simply running the database with default settings, we can also provide some amount of configuration. This is done using a DBConfigurationBuilder to generate the configuration for our database. For example, if we want to specify the port, data directory, and base directory for our database, we run:

DBConfigurationBuilder config = DBConfigurationBuilder.newBuilder();
config.setPort(13306);
config.setDataDir("/tmp/data");
config.setBaseDir("/tmp/db");

DB db = DB.newEmbeddedDB(config.build());

Once we’ve created our DB instance, we can then use it exactly as before.

This also lets us control the version of the database used. We can specify the version using the setDatabaseVersion() method on our DBConfigurationBuilder object. However, if we do this, our application must include the correct dependencies for the database version.

4.2. Interacting With the Database

Once we’ve got the database running, we now need to be able to interact with it. MariaDB4j offers a few different ways to achieve this.

The simplest way is to simply run commands as if we were typing them into the MariaDB terminal. This can be done with the db.run() command. Using this command, we can achieve anything that the database terminal can – including running SQL commands but also more admin-focused tasks:

db.run("show databases");

Note, however, that nothing is returned from this. The command prints the output to the standard Java output instead:

[2024-11-08 09:37:40,470]-[main] INFO  ch.vorburger.mariadb4j.DB - Running a command: show databases
[2024-11-08 09:37:40,470]-[main] INFO  ch.vorburger.exec.ManagedProcess - Starting Program [/tmp/MariaDB4j/base/bin/mariadb, --default-character-set=utf8, --socket=/tmp/MariaDB4j.40315.sock] (in working directory /tmp/MariaDB4j/base)
[2024-11-08 09:37:40,535]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: Database
[2024-11-08 09:37:40,536]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: information_schema
[2024-11-08 09:37:40,536]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: mysql
[2024-11-08 09:37:40,536]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: performance_schema
[2024-11-08 09:37:40,536]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: sys
[2024-11-08 09:37:40,536]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: test
[2024-11-08 09:37:40,536]-[CommonsExecStreamPumper-pool-17-thread-2] INFO  ch.vorburger.exec.ManagedProcess - mariadb: testing
[2024-11-08 09:37:40,538]-[CommonsExecDefaultExecutorpool-14-thread-1] INFO  ch.vorburger.exec.LoggingExecuteResultHandler - Program [/tmp/MariaDB4j/base/bin/mariadb, --default-character-set=utf8, --socket=/tmp/MariaDB4j.40315.sock] (in working directory /tmp/MariaDB4j/base) just exited, with value 0

There are also some helper methods that just wrap db.run() but make it easier to perform some common tasks. For example, db.createDB() can be used to execute the command CREATE DATABASE IF NOT EXISTS:

db.createDB("testing", "testuser", "testpass");

There’s also db.source(), which we can use to execute an entire SQL script that exists on the classpath:

db.createDB("testing", "testuser", "testpass");
db.source("com/baeldung/data/mariadb4j/source.sql", "testuser", "testpass", "testing");

Instead of running a single command, this executes the entire provided file of commands, line by line.

4.3. Interacting With JDBC

Finally, perhaps the most useful way to interact with the database is using JDBC. Since this is a fully running database engine, we can connect to it as we would usually. All we need is an appropriate JDBC connection string and credentials.

We’ve already seen how we can create a database with credentials within our database engine – using db.createDB(). Once done, we can then call db.getConfiguration().getURL() to provide a connection string for talking to our database:

db.createDB("testing", "testuser", "testpass");
assertEquals("jdbc:mariadb://localhost:13306/testing", db.getConfiguration().getURL("testing"));

This generates the URL with the correct hostname and port, even if the port was randomly assigned.

Once we’ve done this, we can then use any standard JDBC utilities to communicate with the database:

String url = db.getConfiguration().getURL("testing");
try (Connection conn = DriverManager.getConnection(url, "testuser", "testpass")) {
    // Use JDBC Connection here.
}

We can do this equally well with any other JDBC-compatible tools – for example, JPA, JdbcTemplate, or anything else.

Note that this assumes that we have the MariaDB JDBC drivers available on the classpath, just as when connecting to any other MariaDB instance.

5. Using Within JUnit Tests

One very powerful use for MariaDB4j is the ability to integrate with a real, but ephemeral database from our tests while the production application points to a permanent database. MariaDB4j provides a JUnit4 rule for exactly this purpose.

Using the standard @Rule annotation starts the database before each test method runs and shuts it down afterwards:

@Rule
public MariaDB4jRule dbRule = new MariaDB4jRule(0);

We can then access the database from within our tests however we wish:

@Test
public void whenRunningATest_thenTheDbIsAvailable() throws Exception {
    try (Connection conn = DriverManager.getConnection(dbRule.getURL(), "root", "")) {
        // Use JDBC Connection here.
    }
}

This will start and stop the database around each individual test method, which gives us isolation but at the cost of performance. Instead, we can use the @ClassRule annotation:

@ClassRule
public static MariaDB4jRule dbRule = new MariaDB4jRule(0);

This starts the database once for the entire test class, instead of for each method. This gives us obvious performance improvements, but at the risk that we might have leakage between tests

6. Summary

This was a quick introduction to MariaDB4j. We’ve seen a few ways that it can be used to easily start up a MariaDB database for use locally. This library can do a lot more, including direct integration with Maven and easy use within Spring Boot.

As usual, all of the examples from this article are available over on GitHub.