Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild
Back to Blog Troubleshooting

IntelliJ H2 In-Memory Database: Why It Looks Empty and How to Fix It

Sean

Platform Writer

Sep 30, 2026
9 min read

IntelliJ shows your H2 in-memory database as empty because an in-memory database exists only inside the JVM that created it, and IntelliJ connects from a different process, so it gets its own blank database instead of your application’s tables.

IntelliJ H2 In-Memory Database: Why It Looks Empty and How to Fix It

It is one of the most common Spring Boot stumbling blocks. The app starts, Hibernate logs the create table statements, the test connection in IntelliJ says success, and then the schema is empty. Nothing is broken. The URL you used describes a database that is private to one process. This guide explains the three ways H2 can run, gives the exact JDBC URL for each, shows how to make IntelliJ see the same data your application sees, and ends with the point most tutorials skip: what to run in production, because H2 is a development tool.

Table of contents

Why the in-memory database looks empty from IntelliJ

H2 supports several ways of running. In embedded mode, the database engine lives inside your application’s JVM. In memory mode, an embedded database keeps its data in RAM and vanishes when the JVM stops or the last connection closes. The URL jdbc:h2:mem:testdb names a database called testdb, but only inside the process that opened it.

When you add an H2 data source in IntelliJ with an in-memory URL, IntelliJ’s own process opens jdbc:h2:mem:testdb. That is a different JVM, so H2 creates a brand new database with the same name and nothing in it. The connection test succeeds because creating an empty database is perfectly valid. You then stare at an empty schema while your application, in its separate process, is happily holding its tables.

The Stack Overflow answer that has been repeated for years is blunt about it: you cannot connect to an in-memory database from an external JVM process. The way out is not to fight that rule but to change how the database is exposed. You either move the data out of pure memory, or you let your application’s JVM act as a server that other tools can reach.

There are four practical fixes, each with a different trade-off. A file database persists data on disk. A TCP server exposes the in-memory database to outside clients. The DB_CLOSE_DELAY setting stops the database vanishing between connections inside your own app. And for Spring Boot, the built-in H2 web console runs inside the application and so sees the right database. The next sections cover each in turn.

Fix one: switch to a file database

The simplest fix is to stop using memory. A file URL stores the database in your project folder, and any process that can read the file can open it, including IntelliJ.

# application.properties
spring.datasource.url=jdbc:h2:file:./data/devdb
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update

In IntelliJ, open the Database tool window, click the plus icon, choose Data Source, then H2. Set the connection type to Embedded, point the file at the same path, and download the driver files if IntelliJ offers to. Now the data source and your app share a file.

The catch is locking. By default H2 file databases allow one process at a time. If your app is running and holding the file, a second connection from IntelliJ may fail with a database-already-in-use error. Adding AUTO_SERVER=TRUE to the URL lets the first process start a server automatically so that others can join.

spring.datasource.url=jdbc:h2:file:./data/devdb;AUTO_SERVER=TRUE

This is a good default for local development because your data survives restarts. That is a feature when you are building a form, and a hazard when a stale schema hides the fact that your migrations no longer work from scratch, so wipe the file now and then.

Fix two: run H2 as a TCP server and connect over the network

If you want to keep the database in memory and still reach it, start an H2 TCP server inside the application. Clients then connect to it by host and port instead of trying to open the database themselves.

import org.h2.tools.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("dev")
public class H2ServerConfig {

    @Bean(initMethod = "start", destroyMethod = "stop")
    public Server h2TcpServer() throws Exception {
        return Server.createTcpServer("-tcp", "-tcpPort", "9092", "-ifNotExists");
    }
}

With the server running, use a TCP URL in IntelliJ. The application keeps using its own in-memory URL, and the server hands IntelliJ the same database.

App:      jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
IntelliJ: jdbc:h2:tcp://localhost:9092/mem:testdb
User:     sa
Password: (empty)

In the IntelliJ H2 data source dialog, pick the Remote connection type, or simply paste the URL by hand. The name after the slash has to match the name the app used. If the app uses Spring Boot’s generated random name, you will not know it, so set spring.datasource.url yourself, or turn off the random name with spring.datasource.generate-unique-name=false so the default stays predictable.

Keep this behind a profile, as shown, and never enable a TCP server that accepts outside connections in an environment other than your own machine. An open database port with the default sa user and an empty password is an open door.

Fix three: DB_CLOSE_DELAY=-1 and the disappearing database

A related symptom looks like the same problem but has a different cause. The tables exist while the app runs, then a second connection finds nothing. By default, H2 drops an in-memory database as soon as the last connection to it closes. If your code opens a connection, closes it, and later opens another, the second one sees a fresh, empty database.

The fix is a URL setting that keeps the database alive for the lifetime of the JVM.

jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1

With a connection pool such as HikariCP the pool keeps at least one connection open, so Spring Boot rarely needs this. It matters in plain JDBC tests, in schema scripts that run on a separate connection, and in any tool that connects briefly. Adding it is harmless, so add it to any in-memory URL you write by hand.

Two more URL options are worth knowing. DB_CLOSE_ON_EXIT=FALSE stops H2 closing the database in a shutdown hook, which helps when a Spring shutdown ordering issue causes errors on exit. And MODE=PostgreSQL or MODE=MySQL asks H2 to imitate that engine’s syntax, which reduces surprises when your real database is different, though it is an approximation and not a guarantee.

Fix four: the Spring Boot H2 console

If you are on Spring Boot and only need to look at your tables, skip IntelliJ’s connection entirely. The H2 web console runs inside your application, so it shares the JVM and sees exactly the database your code sees, including in-memory ones.

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=

Start the app and open http://localhost:8080/h2-console. Use the same JDBC URL as your datasource setting, the sa user and an empty password. If the console loads but shows a blank page or a frame error, Spring Security is usually blocking frames. Allow same-origin frames for the console path in your security configuration, and permit the path itself. The Spring Boot startup log also prints the exact JDBC URL when the datasource name was generated, which is the value to paste into the console.

One more practical note. The IntelliJ Database tool window ships with IntelliJ IDEA Ultimate and DataGrip, not the Community edition. If you are on Community, the web console is your main option, or you can use a standalone SQL client with a TCP or file URL. The JetBrains documentation for H2 lists the connection types the data source dialog supports.

H2 is for development, so plan the real database

H2 is excellent for what it does: it starts in milliseconds, needs no installation, and makes unit tests fast. It is a poor production database. Its SQL dialect differs from Postgres and MySQL in ways that only show up under load or in edge cases, such as case sensitivity, JSON support, locking behaviour and the exact meaning of some types. Code that passes against H2 and fails against Postgres is a very common way to lose a Friday.

The grown-up setup is to run the same engine everywhere. Use a real Postgres or MySQL in production, and use the same engine in tests through a container, so you test the behaviour you will ship. Keep H2 for quick feedback loops if you like, but let schema changes go through migrations that run against the real engine, and store connection details in environment variables rather than in application.properties. The Spring Boot environment variables guide shows how, and the Java backend deployment guide covers getting the service itself online.

How this fits the rest of the stack

A production Spring Boot service needs two things H2 cannot give it: a database that survives restarts and redeploys, and connection details that live outside the code. On RunxBuild that is a Java service beside a managed Postgres or MySQL instance, joined by environment variables, with backups and connection limits handled on the database side. The RunxBuild hosting calculator prices the pair together, so you can see the cost of dropping H2 before you do it. Create the database from the RunxBuild dashboard, then read the managed databases documentation for the connection settings, and the Java services page for the service. Point spring.datasource.url at the new instance, and the empty-schema mystery stops being your problem. Keep an H2 profile for unit tests if you enjoy the speed, and run the integration tests against a real Postgres or MySQL so the two never disagree in production.

Useful related references:

FAQ

Why can I not see my H2 in-memory tables in IntelliJ?

An in-memory H2 database exists only inside the JVM that created it. IntelliJ connects from a separate process, so it creates its own empty database with the same name. Use a file URL, a TCP server, or the Spring Boot H2 console, which runs inside your application.

What does DB_CLOSE_DELAY=-1 do in H2?

It keeps an in-memory database alive until the JVM exits, instead of deleting it when the last connection closes. Without it, opening a second connection after the first has closed gives you a new, empty database. Append it to the URL as jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1.

How do I connect IntelliJ to an in-memory H2 database?

Start an H2 TCP server inside your app, then add an IntelliJ H2 data source with a URL like jdbc:h2:tcp://localhost:9092/mem:testdb and the same user and password. The database name must match the app’s URL. Alternatively use a file database or the web console.

How do I enable the H2 console in Spring Boot?

Add spring.h2.console.enabled=true to application.properties, start the app and open /h2-console. Enter the same JDBC URL as your datasource, user sa and an empty password. If the page is blank, allow same-origin frames and permit the path in Spring Security.

Should I use H2 in production?

No, use it for development and fast tests. Its behaviour differs from Postgres and MySQL in ways that surface under real load, and in-memory data disappears on restart. Run a managed Postgres or MySQL in production and test against the same engine to avoid surprises.

#intellij h2 in memory database#H2 database#Spring Boot#JDBC URL#IntelliJ Database tool