diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DataTooNewException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DataTooNewException.java
new file mode 100644
index 0000000000000000000000000000000000000000..001f28f136807edf3902e6e71dd5366d05bebb18
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DataTooNewException.java
@@ -0,0 +1,7 @@
+package org.briarproject.bramble.api.db;
+
+/**
+ * Thrown when the database uses a newer schema than the current code.
+ */
+public class DataTooNewException extends DbException {
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DataTooOldException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DataTooOldException.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d3e16d33b9cba7f09d983bfb10654a2d700d31f
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DataTooOldException.java
@@ -0,0 +1,8 @@
+package org.briarproject.bramble.api.db;
+
+/**
+ * Thrown when the database uses an older schema than the current code and
+ * cannot be migrated.
+ */
+public class DataTooOldException extends DbException {
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
index f12a0680bbe2164e7bfdcc345039e7bdbcfe804c..ff6ef3885b8835e063be8fe2542dde15b8d77fdf 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
@@ -37,6 +37,11 @@ public interface DatabaseComponent {
 
 	/**
 	 * Opens the database and returns true if the database already existed.
+	 *
+	 * @throws DataTooNewException if the data uses a newer schema than the
+	 * current code
+	 * @throws DataTooOldException if the data uses an older schema than the
+	 * current code and cannot be migrated
 	 */
 	boolean open() throws DbException;
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
index 93e7bf8c685f5bea0fc4fb4127064161c80ee05b..71b351452eeff6224f68a912b2a0211dbd2f28fe 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
@@ -2,6 +2,8 @@ package org.briarproject.bramble.db;
 
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.db.DataTooNewException;
+import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
 import org.briarproject.bramble.api.identity.Author;
@@ -37,6 +39,11 @@ interface Database<T> {
 
 	/**
 	 * Opens the database and returns true if the database already existed.
+	 *
+	 * @throws DataTooNewException if the data uses a newer schema than the
+	 * current code
+	 * @throws DataTooOldException if the data uses an older schema than the
+	 * current code and cannot be migrated
 	 */
 	boolean open() throws DbException;
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index 1f22d75de315dc27649662f92c86ee6d60d7cf0e..de222fa25323a50af5b88d5dc3ac0aff8d39892b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -3,6 +3,8 @@ package org.briarproject.bramble.db;
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.db.DataTooNewException;
+import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DbClosedException;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
@@ -308,7 +310,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		Connection txn = startTransaction();
 		try {
 			if (reopen) {
-				if (!checkSchemaVersion(txn)) throw new DbException();
+				checkSchemaVersion(txn);
 			} else {
 				createTables(txn);
 				storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
@@ -323,16 +325,21 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	/**
 	 * Compares the schema version stored in the database with the schema
-	 * version used by the current code, applies any suitable migrations to the
-	 * data if necessary, and returns true if the schema now matches the
-	 * current code.
+	 * version used by the current code and applies any suitable migrations to
+	 * the data if necessary.
+	 *
+	 * @throws DataTooNewException if the data uses a newer schema than the
+	 * current code
+	 * @throws DataTooOldException if the data uses an older schema than the
+	 * current code and cannot be migrated
 	 */
-	private boolean checkSchemaVersion(Connection txn) throws DbException {
+	private void checkSchemaVersion(Connection txn) throws DbException {
 		Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
 		int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
-		if (dataSchemaVersion == -1) return false;
-		if (dataSchemaVersion == CODE_SCHEMA_VERSION) return true;
-		if (CODE_SCHEMA_VERSION < dataSchemaVersion) return false;
+		if (dataSchemaVersion == -1) throw new DbException();
+		if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
+		if (CODE_SCHEMA_VERSION < dataSchemaVersion)
+			throw new DataTooNewException();
 		// Apply any suitable migrations in order
 		for (Migration<Connection> m : getMigrations()) {
 			int start = m.getStartVersion(), end = m.getEndVersion();
@@ -346,7 +353,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 				dataSchemaVersion = end;
 			}
 		}
-		return dataSchemaVersion == CODE_SCHEMA_VERSION;
+		if (dataSchemaVersion != CODE_SCHEMA_VERSION)
+			throw new DataTooOldException();
 	}
 
 	// Package access for testing
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
index de6cabe05a520c58fd03796e0dd7dbf1f508d188..203f4dfd21f6fd2b09ceed436f3a10dfca9696b7 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
@@ -1,5 +1,7 @@
 package org.briarproject.bramble.db;
 
+import org.briarproject.bramble.api.db.DataTooNewException;
+import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -95,7 +97,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 		db.close();
 	}
 
-	@Test(expected = DbException.class)
+	@Test(expected = DataTooNewException.class)
 	public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
@@ -109,7 +111,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 		db.open();
 	}
 
-	@Test(expected = DbException.class)
+	@Test(expected = DataTooOldException.class)
 	public void testThrowsExceptionIfCodeIsNewerThanDataAndNoMigrations()
 			throws Exception {
 		// Open the DB for the first time
@@ -123,7 +125,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 		db.open();
 	}
 
-	@Test(expected = DbException.class)
+	@Test(expected = DataTooOldException.class)
 	public void testThrowsExceptionIfCodeIsNewerThanDataAndNoSuitableMigration()
 			throws Exception {
 		context.checking(new Expectations() {{