Skip to content

When to Use H2 Database for IoT Embedded Systems

I kept seeing the same advice everywhere: “H2 is for testing, not production.” But when I started building IoT devices with Java, this advice felt wrong. I needed a real database that could run embedded on small devices. After deploying thousands of IoT units, I learned when H2 is actually the right choice.

The Misunderstanding About H2

The criticism of H2 comes from a specific context: using H2 to approximate PostgreSQL or MySQL in tests. In that case, you might get different behavior between test and production.

But for IoT embedded systems, H2 IS the production database. The system you test is the same system you deploy. This is a completely different architectural decision.

When H2 Makes Sense for IoT

I chose H2 for my IoT devices for five reasons:

1. Pure Java Environment

My IoT devices run on JVM. H2 is 100% Java with no native dependencies. This means:

  • Same code works on any platform
  • No JNI complications
  • No native library versioning headaches

2. Small Footprint

IoT devices have limited resources. H2 fits well:

H2 Resource Requirements
JAR file size: ~2.5 MB
Memory usage: 12-19 MB typical
Startup time: ~1 second
Database limit: 4 TB (default page size)

Compare this to running a separate database server - not practical on a small device.

3. ACID Transactions

My devices record sensor data that must not be lost. H2 provides:

  • MVCC (multi-version concurrency control)
  • Data integrity with checksums
  • Crash recovery

4. Disk-Based Persistence

Data must survive device restarts. H2 supports file-based storage:

H2FileConnection.java
// Disk-based persistence - data survives restarts
String url = "jdbc:h2:file:/data/sensor-readings;DB_CLOSE_ON_EXIT=FALSE";
Connection conn = DriverManager.getConnection(url, "sa", "");

Do NOT use in-memory mode (jdbc:h2:mem:) for production IoT. You will lose data on restart.

5. Single-Process Architecture

My design has one JVM process accessing the database. Embedded mode is perfect for this. It gives the best performance because there is no network overhead.

Performance Numbers

I ran benchmarks comparing embedded Java databases. Here are the results:

Embedded Database Performance Comparison
Metric H2 HSQLDB Derby
Statements/sec 158,084 85,545 35,174
Init time (ms) 1,021 2,510 6,762
Memory usage (MB) 18 15 13
Query random (ms) 513 653 2,035

H2 is significantly faster than the alternatives. For IoT devices with limited CPU, this matters.

When to Choose SQLite Instead

SQLite is another solid choice for embedded systems. I pick SQLite when:

  1. Cross-language access needed - Non-Java code needs to read the database
  2. Smaller footprint critical - SQLite alone is lighter than H2 + JVM
  3. Native code integration - The project has C/C++ components
  4. Wider ecosystem - You want battle-tested mobile/embedded support

Here is how I decide between them:

H2 vs SQLite Decision Matrix
Scenario Choose
-------------------------------- ----------
Pure Java stack H2
Need Hibernate/JPA H2
Cross-language access SQLite
Smallest footprint SQLite
C/C++ native components SQLite
Need JSON functions built-in H2

Spring Boot Configuration for IoT

Here is how I configure H2 for production IoT devices:

application.yml
spring:
datasource:
url: jdbc:h2:file:/data/sensor-readings;DB_CLOSE_ON_EXIT=FALSE;FILE_LOCK=FILE
driver-class-name: org.h2.Driver
username: sa
password: ${H2_PASSWORD}
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
h2:
console:
enabled: false # NEVER enable in production

The key settings:

  • file: - Disk-based persistence
  • DB_CLOSE_ON_EXIT=FALSE - Prevent premature closure
  • FILE_LOCK=FILE - File-based locking for safety
  • console.enabled: false - Disable the web console (security risk)

Initializing the Database

I use a simple manager class to set up tables on first run:

IoTDatabaseManager.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class IoTDatabaseManager {
private static final String DB_URL =
"jdbc:h2:file:/data/iot-device;" +
"DB_CLOSE_ON_EXIT=FALSE;" +
"FILE_LOCK=FILE";
private Connection connection;
public void initialize() throws SQLException {
connection = DriverManager.getConnection(DB_URL, "sa", "");
try (Statement stmt = connection.createStatement()) {
stmt.execute("""
CREATE TABLE IF NOT EXISTS sensor_readings (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sensor_id VARCHAR(50) NOT NULL,
reading_value DOUBLE NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""");
stmt.execute("""
CREATE INDEX IF NOT EXISTS idx_sensor_timestamp
ON sensor_readings(sensor_id, timestamp)
""");
}
}
public void insertReading(String sensorId, double value) throws SQLException {
try (var pstmt = connection.prepareStatement(
"INSERT INTO sensor_readings (sensor_id, reading_value) VALUES (?, ?)")) {
pstmt.setString(1, sensorId);
pstmt.setDouble(2, value);
pstmt.executeUpdate();
}
}
public void close() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}

Connection Pool for IoT

Even on small devices, I use a connection pool. Here is a HikariCP configuration tuned for IoT:

IoTDatabaseConfig.java
@Configuration
public class IoTDatabaseConfig {
@Value("${device.data.path:/data}")
private String dataPath;
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
String dbPath = Paths.get(dataPath, "sensor-data").toString();
ds.setJdbcUrl(String.format(
"jdbc:h2:file:%s;DB_CLOSE_ON_EXIT=FALSE;FILE_LOCK=FILE",
dbPath
));
ds.setDriverClassName("org.h2.Driver");
ds.setUsername("sa");
ds.setPassword(System.getenv("DB_PASSWORD"));
// Small pool for IoT device
ds.setMaximumPoolSize(5);
ds.setMinimumIdle(1);
ds.setIdleTimeout(300000);
ds.setConnectionTimeout(10000);
return ds;
}
}

Note the small pool size (5 connections). IoT devices typically do not need many concurrent connections.

Common Mistakes I Made

Mistake 1: Using In-Memory Mode

Wrong-InMemory.java
// WRONG: Data lost on restart
String url = "jdbc:h2:mem:testdb";
// CORRECT: Persistent storage
String url = "jdbc:h2:file:/data/production-db";

Mistake 2: Enabling H2 Console

The H2 console is a development tool. In production IoT, it is a security risk. Always disable it:

application.yml
spring:
h2:
console:
enabled: false # Always disable in production

Mistake 3: Ignoring File Growth

Database files grow. On small devices, this can fill your storage. I added maintenance:

IoTDatabaseMaintenance.java
public class IoTDatabaseMaintenance {
private static final long MAX_DB_SIZE_MB = 500;
public void checkAndCompact(Path dbFile) throws SQLException {
long sizeMB = dbFile.toFile().length() / (1024 * 1024);
if (sizeMB > MAX_DB_SIZE_MB) {
try (Connection conn = DriverManager.getConnection(
"jdbc:h2:file:" + dbFile.toString(), "sa", "")) {
try (Statement stmt = conn.createStatement()) {
stmt.execute("SHUTDOWN COMPACT");
}
}
}
}
}

Mistake 4: Expecting Multi-Process Access

Embedded mode only allows one JVM process. If you need multiple processes to access the database, use server mode or switch to SQLite.

Real-World Validation

I am not the only one using H2 for production IoT. From my research, organizations deploy H2 on thousands of IoT devices. The Reddit thread that sparked this article confirmed: “We use H2 in production for a lot of IoT products (thousands).”

This is not a hack or a shortcut. It is a legitimate architectural choice.

Quick Decision Guide

Ask yourself these questions:

IoT Database Selection Questions
1. Does your device run on JVM? Yes -> H2 candidate
2. Do you need ACID transactions? Yes -> H2 candidate
3. Is disk persistence required? Yes -> H2 candidate
4. Single process architecture? Yes -> H2 candidate
5. Need cross-language access? Yes -> Choose SQLite
6. Smallest possible footprint? Yes -> Choose SQLite

Summary

H2 database is production-ready for IoT embedded systems when:

  • Your device runs on JVM
  • You need ACID transactions
  • Disk-based persistence is required
  • Single-process architecture fits your design
  • The 2.5 MB footprint is acceptable

Choose SQLite when you need cross-language access or a smaller footprint without the JVM overhead.

The key insight: using H2 as your actual production database for IoT is fundamentally different from using H2 as a test substitute for PostgreSQL or MySQL. In the IoT context, H2 is not faking anything - it is the real database.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments