JPhotoTagger now supports SQLite as an alternative database backend to HSQLDB. This guide explains how to migrate your existing HSQLDB database to SQLite, switch between database backends, and what to expect during the migration process.
SQLite offers several advantages over HSQLDB:
Based on benchmark testing with 1000 records:
| Operation Type | Performance vs HSQLDB | Notes |
|---|---|---|
| Point Lookups (SELECT with WHERE) | 11% faster | More consistent performance |
| Filtered Queries | 4-9% faster | Better index utilization |
| Single Inserts | 2.35x slower | Still very fast (<20 microseconds) |
| Full Table Scans | 6.5x slower | Optimization recommended for large datasets |
Overall: SQLite provides better consistency and comparable or improved performance for typical application workflows. Full table scans are slower but unlikely to impact normal usage.
This method allows you to test SQLite without permanently changing your configuration:
Start JPhotoTagger with SQLite backend:
java -Djphototagger.database.backend=sqlite -jar jphototagger.jar
Verify SQLite is active: Check the application logs or database file location
Return to HSQLDB: Simply restart without the system property
java -jar jphototagger.jar
Note: The system property creates a new empty SQLite database. To migrate your existing data, use Method 3.
You can set the database backend preference programmatically (this persists across sessions):
import org.jphototagger.domain.repository.DatabaseBackend;
import org.jphototagger.domain.repository.DatabaseBackendPreference;
// Switch to SQLite
DatabaseBackendPreference.setPreference(DatabaseBackend.SQLITE);
// Switch back to HSQLDB
DatabaseBackendPreference.setPreference(DatabaseBackend.HSQLDB);
// Clear preference (reverts to default HSQLDB)
DatabaseBackendPreference.clearPreference();
Priority: System property (-Djphototagger.database.backend) always takes precedence over stored preferences.
To migrate your existing HSQLDB database to SQLite:
Default locations:
~/.jphototagger/database/jphototagger%USERPROFILE%\.jphototagger\database\jphototagger~/Library/Application Support/jphototagger/database/jphototaggerYou should see files like:
jphototagger.scriptjphototagger.datajphototagger.propertiesCRITICAL: Always backup before migration!
# Linux/macOS
cp -r ~/.jphototagger/database ~/.jphototagger/database.backup
# Windows
xcopy %USERPROFILE%\.jphototagger\database %USERPROFILE%\.jphototagger\database.backup /E /I
Using Gradle (from source):
./gradlew :Tools:MigrationTool:run --args="~/.jphototagger/database/jphototagger ~/.jphototagger/database/jphototagger.db"
Using standalone JAR:
java -jar MigrationTool.jar /path/to/hsqldb/jphototagger /path/to/output/jphototagger.db
Parameters:
The migration tool will display:
JPhotoTagger Database Migration Tool
=====================================
Source HSQLDB: /home/user/.jphototagger/database/jphototagger
Target SQLite: /home/user/.jphototagger/database/jphototagger.db
Starting migration...
Migrating table: files
Completed: files (1234 rows)
Migrating table: xmp
Completed: xmp (1234 rows)
...
Migration completed successfully!
Tables migrated: 35
Total rows: 12345
SQLite database created at: /home/user/.jphototagger/database/jphototagger.db
Add system property or set preference:
java -Djphototagger.database.backend=sqlite -jar jphototagger.jar
Or programmatically:
DatabaseBackendPreference.setPreference(DatabaseBackend.SQLITE);
The SQLite schema is semantically equivalent to HSQLDB but uses SQLite-specific syntax:
BIGINT GENERATED BY DEFAULT AS IDENTITY → INTEGER PRIMARY KEY AUTOINCREMENTVARCHAR_IGNORECASE → TEXT COLLATE NOCASEVARBINARY → BLOBINTEGER PRIMARY KEY AUTOINCREMENT which may generate different IDs than HSQLDB (but referential integrity is maintained)COLLATE NOCASE for case-insensitive text matching.db file plus .db-wal and .db-shm files (WAL mode)If you need to return to HSQLDB:
Simply remove the system property:
# Instead of:
java -Djphototagger.database.backend=sqlite -jar jphototagger.jar
# Use:
java -jar jphototagger.jar
DatabaseBackendPreference.clearPreference();
If you experience issues:
Stop JPhotoTagger
Delete or rename the SQLite database:
rm ~/.jphototagger/database/jphototagger.db
rm ~/.jphototagger/database/jphototagger.db-wal
rm ~/.jphototagger/database/jphototagger.db-shm
Restore HSQLDB from backup (if needed):
cp -r ~/.jphototagger/database.backup/* ~/.jphototagger/database/
Clear preference or system property
Restart JPhotoTagger
Note: Your HSQLDB database is never modified by the migration tool. It only reads from HSQLDB and writes to a new SQLite file.
Error: HSQLDB database files not found
.script or .data files existError: Could not create output directory
Migration failed with "Table not found"
"Connection factory is closed"
Slow performance on large datasets
VACUUM on SQLite database to optimizeMissing data after migration
To verify data integrity, you can run SQL queries:
-- Count files
SELECT COUNT(*) FROM files;
-- Count XMP records
SELECT COUNT(*) FROM xmp;
-- Count keywords
SELECT COUNT(*) FROM dc_subjects;
-- Check for orphaned records (should be 0)
SELECT COUNT(*) FROM xmp WHERE id_file NOT IN (SELECT id FROM files);
The SQLite implementation uses these optimizations by default:
If you experience performance issues, you can tune SQLite pragmas:
PRAGMA cache_size = -64000; -- 64MB cache
PRAGMA temp_store = MEMORY; -- Use memory for temp tables
PRAGMA mmap_size = 268435456; -- 256MB memory-mapped I/O
Caution: These settings require manual modification and may impact stability.
For slow queries:
EXPLAIN QUERY PLAN to check index usageA: No. The migration tool only reads from HSQLDB and creates a new SQLite file. Your original database is never modified.
A: Yes. Simply remove the system property or clear the preference. Your HSQLDB database remains intact.
A: Approximately 1-2 minutes per 10,000 images. A typical 5,000 image database migrates in under a minute.
A: Nothing. The database stores file paths, not images. Migration only moves metadata and references.
A: For most operations (point lookups, filtered queries), yes. Full table scans are slower but rarely used in normal workflows.
A: No. JPhotoTagger uses one database backend at a time, selected by system property or preference.
A: The migration tool creates a new SQLite file. If it fails, simply delete the incomplete .db file and try again. Your HSQLDB database is unaffected.
A: No. Only the database is migrated. Images remain in their original locations.
A: Yes. Plugins use the repository interface, which works identically with both backends.
A: The migration tool reports table and row counts. Compare these numbers with your HSQLDB database size. Also verify data in the application.
A: Not directly. If you need to go back, use your HSQLDB backup. There's currently no SQLite-to-HSQLDB migration tool.
A: These are SQLite Write-Ahead Log and Shared Memory files. They're created automatically in WAL mode and should not be deleted while the database is in use.
If you encounter issues during migration:
HSQLDB (multiple files):
jphototagger.script - Schema and small tablesjphototagger.data - Large table data (cached tables)jphototagger.properties - Database propertiesjphototagger.log - Transaction logSQLite (single file + WAL):
jphototagger.db - Main database filejphototagger.db-wal - Write-Ahead Log (transient)jphototagger.db-shm - Shared memory (transient)The database backend selection uses a three-tier priority system:
-Djphototagger.database.backend=sqliteDatabaseBackendPreference.setPreference(DatabaseBackend.SQLITE)The migration tool:
Document Version: 1.0 Last Updated: 2025-11-29 Applicable to: JPhotoTagger Phase 4 (SQLite Migration)