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:
JAR file size: ~2.5 MBMemory usage: 12-19 MB typicalStartup time: ~1 secondDatabase 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:
// Disk-based persistence - data survives restartsString 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:
Metric H2 HSQLDB DerbyStatements/sec 158,084 85,545 35,174Init time (ms) 1,021 2,510 6,762Memory usage (MB) 18 15 13Query random (ms) 513 653 2,035H2 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:
- Cross-language access needed - Non-Java code needs to read the database
- Smaller footprint critical - SQLite alone is lighter than H2 + JVM
- Native code integration - The project has C/C++ components
- Wider ecosystem - You want battle-tested mobile/embedded support
Here is how I decide between them:
Scenario Choose-------------------------------- ----------Pure Java stack H2Need Hibernate/JPA H2Cross-language access SQLiteSmallest footprint SQLiteC/C++ native components SQLiteNeed JSON functions built-in H2Spring Boot Configuration for IoT
Here is how I configure H2 for production IoT devices:
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 productionThe key settings:
file:- Disk-based persistenceDB_CLOSE_ON_EXIT=FALSE- Prevent premature closureFILE_LOCK=FILE- File-based locking for safetyconsole.enabled: false- Disable the web console (security risk)
Initializing the Database
I use a simple manager class to set up tables on first run:
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:
@Configurationpublic 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: Data lost on restartString url = "jdbc:h2:mem:testdb";
// CORRECT: Persistent storageString 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:
spring: h2: console: enabled: false # Always disable in productionMistake 3: Ignoring File Growth
Database files grow. On small devices, this can fill your storage. I added maintenance:
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:
1. Does your device run on JVM? Yes -> H2 candidate2. Do you need ACID transactions? Yes -> H2 candidate3. Is disk persistence required? Yes -> H2 candidate4. Single process architecture? Yes -> H2 candidate5. Need cross-language access? Yes -> Choose SQLite6. Smallest possible footprint? Yes -> Choose SQLiteSummary
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