diff --git a/briar-api/src/org/briarproject/api/Settings.java b/briar-api/src/org/briarproject/api/Settings.java
new file mode 100644
index 0000000000000000000000000000000000000000..465a0c300cd4cc56bee477d7464e70ed08c00c2a
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/Settings.java
@@ -0,0 +1,6 @@
+package org.briarproject.api;
+
+public class Settings extends StringMap {
+
+	private static final long serialVersionUID = 8439364293077111359L;
+}
diff --git a/briar-api/src/org/briarproject/api/StringMap.java b/briar-api/src/org/briarproject/api/StringMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..63685438ec262e54ed4902fc3781c78249066872
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/StringMap.java
@@ -0,0 +1,29 @@
+package org.briarproject.api;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+abstract class StringMap extends Hashtable<String, String> {
+
+	private static final long serialVersionUID = 2497176435908100448L;
+
+	protected StringMap(Map<String, String> m) {
+		super(m);
+	}
+
+	protected StringMap() {
+		super();
+	}
+
+	public boolean getBoolean(String key, boolean defaultValue) {
+		String s = get(key);
+		if(s == null) return defaultValue;
+		if("true".equals(s)) return true;
+		if("false".equals(s)) return false;
+		return defaultValue;
+	}
+
+	public void putBoolean(String key, boolean value) {
+		put(key, String.valueOf(value));
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/TransportConfig.java b/briar-api/src/org/briarproject/api/TransportConfig.java
index f806fca42d060f040faf9754868e4818a52ca04c..7997ede7151384730bbda6ba53016565256b05f9 100644
--- a/briar-api/src/org/briarproject/api/TransportConfig.java
+++ b/briar-api/src/org/briarproject/api/TransportConfig.java
@@ -1,15 +1,16 @@
 package org.briarproject.api;
 
-import java.util.Hashtable;
 import java.util.Map;
 
-public class TransportConfig extends Hashtable<String, String> {
+public class TransportConfig extends StringMap {
 
 	private static final long serialVersionUID = 2330384620787778596L;
 
-	public TransportConfig(Map<String, String> c) {
-		super(c);
+	public TransportConfig(Map<String, String> m) {
+		super(m);
 	}
 
-	public TransportConfig() {}
+	public TransportConfig() {
+		super();
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/TransportProperties.java b/briar-api/src/org/briarproject/api/TransportProperties.java
index b934c7ed53873bfdc8d722adf60d430481aa7e11..7d05515e8e92502ac423a38c9e8a38849394e869 100644
--- a/briar-api/src/org/briarproject/api/TransportProperties.java
+++ b/briar-api/src/org/briarproject/api/TransportProperties.java
@@ -1,15 +1,16 @@
 package org.briarproject.api;
 
-import java.util.Hashtable;
 import java.util.Map;
 
-public class TransportProperties extends Hashtable<String, String> {
+public class TransportProperties extends StringMap {
 
 	private static final long serialVersionUID = 7533739534204953625L;
 
-	public TransportProperties(Map<String, String> p) {
-		super(p);
+	public TransportProperties(Map<String, String> m) {
+		super(m);
 	}
 
-	public TransportProperties() {}
+	public TransportProperties() {
+		super();
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 63bd0d8538db605b376aa969b2ec76042613559f..78fbc135864f69e1f4230790085e7481f62d6be1 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -9,6 +9,7 @@ import org.briarproject.api.AuthorId;
 import org.briarproject.api.Contact;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.LocalAuthor;
+import org.briarproject.api.Settings;
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
@@ -233,6 +234,9 @@ public interface DatabaseComponent {
 	/** Returns all temporary secrets. */
 	Collection<TemporarySecret> getSecrets() throws DbException;
 
+	/** Returns all settings. */
+	Settings getSettings() throws DbException;
+
 	/** Returns the maximum latencies of all local transports. */
 	Map<TransportId, Long> getTransportLatencies() throws DbException;
 
@@ -263,6 +267,9 @@ public interface DatabaseComponent {
 	void mergeLocalProperties(TransportId t, TransportProperties p)
 			throws DbException;
 
+	/** Merges the given settings with the existing settings. */
+	void mergeSettings(Settings s) throws DbException;
+
 	/** Processes an ack from the given contact. */
 	void receiveAck(ContactId c, Ack a) throws DbException;
 
diff --git a/briar-api/src/org/briarproject/api/event/SettingsUpdatedEvent.java b/briar-api/src/org/briarproject/api/event/SettingsUpdatedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f369891b5e876bac5bc0a2497955699c59d5284
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/SettingsUpdatedEvent.java
@@ -0,0 +1,6 @@
+package org.briarproject.api.event;
+
+/** An event that is broadcast when one or more settings are updated. */
+public class SettingsUpdatedEvent extends Event {
+
+}
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index aa46a20bf58abc3310c28ae83949b086e59fbd6d..5679553fef2166ea12bbbae9c9ae106bbb7f94f8 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -9,6 +9,7 @@ import org.briarproject.api.AuthorId;
 import org.briarproject.api.Contact;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.LocalAuthor;
+import org.briarproject.api.Settings;
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
@@ -44,6 +45,7 @@ import org.briarproject.api.transport.TemporarySecret;
  * <li> identity
  * <li> message
  * <li> retention
+ * <li> setting
  * <li> subscription
  * <li> transport
  * <li> window
@@ -485,6 +487,13 @@ interface Database<T> {
 	 */
 	Collection<TemporarySecret> getSecrets(T txn) throws DbException;
 
+	/**
+	 * Returns all settings.
+	 * <p>
+	 * Locking: setting read.
+	 */
+	Settings getSettings(T txn) throws DbException;
+
 	/**
 	 * Returns a subscription ack for the given contact, or null if no ack is
 	 * due.
@@ -596,6 +605,13 @@ interface Database<T> {
 	void mergeLocalProperties(T txn, TransportId t, TransportProperties p)
 			throws DbException;
 
+	/**
+	 * Merges the given settings with the existing settings.
+	 * <p>
+	 * Locking: setting write.
+	 */
+	void mergeSettings(T txn, Settings s) throws DbException;
+
 	/**
 	 * Marks a message as needing to be acknowledged to the given contact.
 	 * <p>
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index a963367c957006523e9df03173865e37e4b841c7..872e55d15458c8756a4b229e0e9695304f6f59d0 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -26,6 +26,7 @@ import org.briarproject.api.AuthorId;
 import org.briarproject.api.Contact;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.LocalAuthor;
+import org.briarproject.api.Settings;
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
@@ -55,6 +56,7 @@ import org.briarproject.api.event.MessageToRequestEvent;
 import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent;
 import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.RemoteTransportsUpdatedEvent;
+import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.api.event.SubscriptionAddedEvent;
 import org.briarproject.api.event.SubscriptionRemovedEvent;
 import org.briarproject.api.event.TransportAddedEvent;
@@ -104,6 +106,8 @@ DatabaseCleaner.Callback {
 			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock retentionLock =
 			new ReentrantReadWriteLock(true);
+	private final ReentrantReadWriteLock settingLock =
+			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock subscriptionLock =
 			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock transportLock =
@@ -1152,6 +1156,23 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public Settings getSettings() throws DbException {
+		settingLock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				Settings s = db.getSettings(txn);
+				db.commitTransaction(txn);
+				return s;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			settingLock.readLock().unlock();
+		}
+	}
+
 	public Map<TransportId, Long> getTransportLatencies() throws DbException {
 		transportLock.readLock().lock();
 		try {
@@ -1286,6 +1307,27 @@ DatabaseCleaner.Callback {
 		if(changed) callListeners(new LocalTransportsUpdatedEvent());
 	}
 
+	public void mergeSettings(Settings s) throws DbException {
+		boolean changed = false;
+		settingLock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if(!s.equals(db.getSettings(txn))) {
+					db.mergeSettings(txn, s);
+					changed = true;
+				}
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			settingLock.writeLock().unlock();
+		}
+		if(changed) callListeners(new SettingsUpdatedEvent());
+	}
+
 	public void receiveAck(ContactId c, Ack a) throws DbException {
 		contactLock.readLock().lock();
 		try {
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 5fb6436c5f33b3dd9c3c08bafda0c1391957d953..6979dfc268f6deb5c1fd143500605a58fc30e784 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -35,6 +35,7 @@ import org.briarproject.api.AuthorId;
 import org.briarproject.api.Contact;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.LocalAuthor;
+import org.briarproject.api.Settings;
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
@@ -378,10 +379,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 				if(!checkSchemaVersion(txn)) throw new DbException();
 			} else {
 				createTables(txn);
-				String schemaVersion = String.valueOf(SCHEMA_VERSION);
-				setSetting(txn, "schemaVersion", schemaVersion);
-				String minSchemaVersion = String.valueOf(MIN_SCHEMA_VERSION);
-				setSetting(txn, "minSchemaVersion", minSchemaVersion);
+				Settings s = new Settings();
+				s.put("schemaVersion", String.valueOf(SCHEMA_VERSION));
+				s.put("minSchemaVersion", String.valueOf(MIN_SCHEMA_VERSION));
+				mergeSettings(txn, s);
 			}
 			commitTransaction(txn);
 		} catch(DbException e) {
@@ -392,56 +393,17 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	private boolean checkSchemaVersion(Connection txn) throws DbException {
 		try {
-			String value = getSetting(txn, "schemaVersion");
-			int schemaVersion = Integer.valueOf(value);
+			Settings s = getSettings(txn);
+			int schemaVersion = Integer.valueOf(s.get("schemaVersion"));
 			if(schemaVersion == SCHEMA_VERSION) return true;
 			if(schemaVersion < MIN_SCHEMA_VERSION) return false;
-			value = getSetting(txn, "minSchemaVersion");
-			int minSchemaVersion = Integer.valueOf(value);
+			int minSchemaVersion = Integer.valueOf(s.get("minSchemaVersion"));
 			return SCHEMA_VERSION >= minSchemaVersion;
 		} catch(NumberFormatException e) {
 			throw new DbException(e);
 		}
 	}
 
-	private String getSetting(Connection txn, String key) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT value FROM settings WHERE key = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setString(1, key);
-			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			String value = rs.getString(1);
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			return value;
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
-	private void setSetting(Connection txn, String key, String value)
-			throws DbException {
-		PreparedStatement ps = null;
-		try {
-			String sql = "INSERT INTO settings (key, value) VALUES (?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setString(1, key);
-			ps.setString(2, value);
-			int affected = ps.executeUpdate();
-			if(affected < 1) throw new DbStateException();
-			ps.close();
-		} catch(SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	private void tryToClose(ResultSet rs) {
 		if(rs != null) try {
 			rs.close();
@@ -2215,6 +2177,25 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public Settings getSettings(Connection txn) throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT key, value FROM settings";
+			ps = txn.prepareStatement(sql);
+			rs = ps.executeQuery();
+			Settings s = new Settings();
+			while(rs.next()) s.put(rs.getString(1), rs.getString(2));
+			rs.close();
+			ps.close();
+			return s;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public SubscriptionAck getSubscriptionAck(Connection txn, ContactId c)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -2657,6 +2638,48 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void mergeSettings(Connection txn, Settings s) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			// Update any settings that already exist
+			String sql = "UPDATE settings SET value = ? WHERE key = ?";
+			ps = txn.prepareStatement(sql);
+			for(Entry<String, String> e : s.entrySet()) {
+				ps.setString(1, e.getValue());
+				ps.setString(2, e.getKey());
+				ps.addBatch();
+			}
+			int[] batchAffected = ps.executeBatch();
+			if(batchAffected.length != s.size()) throw new DbStateException();
+			for(int i = 0; i < batchAffected.length; i++) {
+				if(batchAffected[i] < 0) throw new DbStateException();
+				if(batchAffected[i] > 1) throw new DbStateException();
+			}
+			// Insert any settings that don't already exist
+			sql = "INSERT INTO settings (key, value) VALUES (?, ?)";
+			ps = txn.prepareStatement(sql);
+			int updateIndex = 0, inserted = 0;
+			for(Entry<String, String> e : s.entrySet()) {
+				if(batchAffected[updateIndex] == 0) {
+					ps.setString(1, e.getKey());
+					ps.setString(2, e.getValue());
+					ps.addBatch();
+					inserted++;
+				}
+				updateIndex++;
+			}
+			batchAffected = ps.executeBatch();
+			if(batchAffected.length != inserted) throw new DbStateException();
+			for(int i = 0; i < batchAffected.length; i++) {
+				if(batchAffected[i] != 1) throw new DbStateException();
+			}
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void raiseAckFlag(Connection txn, ContactId c, MessageId m)
 			throws DbException {
 		PreparedStatement ps = null;