diff --git a/briar-api/src/org/briarproject/api/data/MetadataEncoder.java b/briar-api/src/org/briarproject/api/data/MetadataEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b2e3ab8e41f59ff35a41e682661f155a49b5130
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/data/MetadataEncoder.java
@@ -0,0 +1,9 @@
+package org.briarproject.api.data;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.db.Metadata;
+
+public interface MetadataEncoder {
+
+	Metadata encode(BdfDictionary d) throws FormatException;
+}
diff --git a/briar-core/src/org/briarproject/data/DataModule.java b/briar-core/src/org/briarproject/data/DataModule.java
index 85d7b311a0cfc508a9b215a6b651d848202615f7..c3fe841aee351adf938a17fd13dae8a095da52b1 100644
--- a/briar-core/src/org/briarproject/data/DataModule.java
+++ b/briar-core/src/org/briarproject/data/DataModule.java
@@ -4,6 +4,7 @@ import com.google.inject.AbstractModule;
 
 import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.BdfWriterFactory;
+import org.briarproject.api.data.MetadataEncoder;
 import org.briarproject.api.data.MetadataParser;
 
 public class DataModule extends AbstractModule {
@@ -13,5 +14,6 @@ public class DataModule extends AbstractModule {
 		bind(BdfReaderFactory.class).to(BdfReaderFactoryImpl.class);
 		bind(BdfWriterFactory.class).to(BdfWriterFactoryImpl.class);
 		bind(MetadataParser.class).to(MetadataParserImpl.class);
+		bind(MetadataEncoder.class).to(MetadataEncoderImpl.class);
 	}
 }
diff --git a/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java b/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..313b62f4b1752e6b26fe7b45dc233314ae430085
--- /dev/null
+++ b/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java
@@ -0,0 +1,173 @@
+package org.briarproject.data;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.db.Metadata;
+import org.briarproject.util.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
+
+import static org.briarproject.data.Types.DICTIONARY;
+import static org.briarproject.data.Types.END;
+import static org.briarproject.data.Types.FALSE;
+import static org.briarproject.data.Types.FLOAT_64;
+import static org.briarproject.data.Types.INT_16;
+import static org.briarproject.data.Types.INT_32;
+import static org.briarproject.data.Types.INT_64;
+import static org.briarproject.data.Types.INT_8;
+import static org.briarproject.data.Types.LIST;
+import static org.briarproject.data.Types.NULL;
+import static org.briarproject.data.Types.RAW_16;
+import static org.briarproject.data.Types.RAW_32;
+import static org.briarproject.data.Types.RAW_8;
+import static org.briarproject.data.Types.STRING_16;
+import static org.briarproject.data.Types.STRING_32;
+import static org.briarproject.data.Types.STRING_8;
+import static org.briarproject.data.Types.TRUE;
+
+class MetadataEncoderImpl implements MetadataEncoder {
+
+	@Override
+	public Metadata encode(BdfDictionary d) throws FormatException {
+		Metadata m = new Metadata();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		for (Map.Entry<String, Object> e : d.entrySet()) {
+			if (e.getValue() == null) {
+				// Special case: if the value is null, the key is being removed
+				m.put(e.getKey(), null);
+			} else {
+				encodeObject(out, e.getValue());
+				m.put(e.getKey(), out.toByteArray());
+				out.reset();
+			}
+		}
+		return m;
+	}
+
+	private void encodeObject(ByteArrayOutputStream out, Object o)
+			throws FormatException {
+		if (o == null) out.write(NULL);
+		else if (o instanceof Boolean) out.write((Boolean) o ? TRUE : FALSE);
+		else if (o instanceof Byte) encodeInteger(out, (Byte) o);
+		else if (o instanceof Short) encodeInteger(out, (Short) o);
+		else if (o instanceof Integer) encodeInteger(out, (Integer) o);
+		else if (o instanceof Long) encodeInteger(out, (Long) o);
+		else if (o instanceof Float) encodeFloat(out, (Float) o);
+		else if (o instanceof Double) encodeFloat(out, (Double) o);
+		else if (o instanceof String) encodeString(out, (String) o);
+		else if (o instanceof byte[]) encodeRaw(out, (byte[]) o);
+		else if (o instanceof BdfList) encodeList(out, (BdfList) o);
+		else if (o instanceof BdfDictionary) encodeDictionary(out,
+				(BdfDictionary) o);
+		else throw new FormatException();
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, byte i) {
+		out.write(INT_8);
+		out.write(i);
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, short i) {
+		if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
+			encodeInteger(out, (byte) i);
+		} else {
+			out.write(INT_16);
+			out.write((byte) (i >> 8));
+			out.write((byte) ((i << 8) >> 8));
+		}
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, int i) {
+		if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
+			encodeInteger(out, (short) i);
+		} else {
+			out.write(INT_32);
+			out.write((byte) (i >> 24));
+			out.write((byte) ((i << 8) >> 24));
+			out.write((byte) ((i << 16) >> 24));
+			out.write((byte) ((i << 24) >> 24));
+		}
+	}
+
+	private void encodeInteger(ByteArrayOutputStream out, long i) {
+		if (i >= Integer.MIN_VALUE && i <= Integer.MAX_VALUE) {
+			encodeInteger(out, (int) i);
+		} else {
+			out.write(INT_64);
+			out.write((byte) (i >> 56));
+			out.write((byte) ((i << 8) >> 56));
+			out.write((byte) ((i << 16) >> 56));
+			out.write((byte) ((i << 24) >> 56));
+			out.write((byte) ((i << 32) >> 56));
+			out.write((byte) ((i << 40) >> 56));
+			out.write((byte) ((i << 48) >> 56));
+			out.write((byte) ((i << 56) >> 56));
+		}
+	}
+
+	private void encodeFloat(ByteArrayOutputStream out, float f) {
+		encodeFloat(out, (double) f);
+	}
+
+	private void encodeFloat(ByteArrayOutputStream out, double d) {
+		long i = Double.doubleToLongBits(d);
+		out.write(FLOAT_64);
+		out.write((byte) (i >> 56));
+		out.write((byte) ((i << 8) >> 56));
+		out.write((byte) ((i << 16) >> 56));
+		out.write((byte) ((i << 24) >> 56));
+		out.write((byte) ((i << 32) >> 56));
+		out.write((byte) ((i << 40) >> 56));
+		out.write((byte) ((i << 48) >> 56));
+		out.write((byte) ((i << 56) >> 56));
+	}
+
+	private void encodeString(ByteArrayOutputStream out, String s) {
+		byte[] b = StringUtils.toUtf8(s);
+		if (b.length <= Byte.MAX_VALUE) {
+			out.write(STRING_8);
+			encodeInteger(out, (byte) b.length);
+		} else if (b.length <= Short.MAX_VALUE) {
+			out.write(STRING_16);
+			encodeInteger(out, (short) b.length);
+		} else {
+			out.write(STRING_32);
+			encodeInteger(out, b.length);
+		}
+		out.write(b, 0, b.length);
+	}
+
+	private void encodeRaw(ByteArrayOutputStream out, byte[] b) {
+		if (b.length <= Byte.MAX_VALUE) {
+			out.write(RAW_8);
+			encodeInteger(out, (byte) b.length);
+		} else if (b.length <= Short.MAX_VALUE) {
+			out.write(RAW_16);
+			encodeInteger(out, (short) b.length);
+		} else {
+			out.write(RAW_32);
+			encodeInteger(out, b.length);
+		}
+		out.write(b, 0, b.length);
+	}
+
+	private void encodeList(ByteArrayOutputStream out, BdfList list)
+			throws FormatException {
+		out.write(LIST);
+		for (Object o : list) encodeObject(out, o);
+		out.write(END);
+	}
+
+	private void encodeDictionary(ByteArrayOutputStream out,
+			BdfDictionary dict) throws FormatException {
+		out.write(DICTIONARY);
+		for (Map.Entry<String, Object> e : dict.entrySet()) {
+			encodeString(out, e.getKey());
+			encodeObject(out, e.getValue());
+		}
+		out.write(END);
+	}
+}
diff --git a/briar-core/src/org/briarproject/data/MetadataParserImpl.java b/briar-core/src/org/briarproject/data/MetadataParserImpl.java
index 954838072c98c90489f3eb6ee088a00df70031ab..e705bcca597c65caf31a8bdb79666fb7d5ef0289 100644
--- a/briar-core/src/org/briarproject/data/MetadataParserImpl.java
+++ b/briar-core/src/org/briarproject/data/MetadataParserImpl.java
@@ -5,9 +5,9 @@ import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataParser;
 import org.briarproject.api.db.Metadata;
+import org.briarproject.util.StringUtils;
 
 import java.io.ByteArrayInputStream;
-import java.io.UnsupportedEncodingException;
 import java.util.Map;
 
 import static org.briarproject.data.Types.DICTIONARY;
@@ -129,11 +129,7 @@ class MetadataParserImpl implements MetadataParser {
 		if (len < 0) throw new FormatException();
 		byte[] b = new byte[len];
 		if (in.read(b, 0, len) != len) throw new FormatException();
-		try {
-			return new String(b, 0, len, "UTF-8");
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException();
-		}
+		return StringUtils.fromUtf8(b, 0, len);
 	}
 
 	private byte[] parseRaw(ByteArrayInputStream in, int len)
diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java
index 0f62c62a8f09470e823964c54c8168989d7af2c0..822831ac05b347daa88b041144a6bf100a93cd04 100644
--- a/briar-core/src/org/briarproject/util/StringUtils.java
+++ b/briar-core/src/org/briarproject/util/StringUtils.java
@@ -39,6 +39,14 @@ public class StringUtils {
 		}
 	}
 
+	public static String fromUtf8(byte[] bytes, int off, int len) {
+		try {
+			return new String(bytes, off, len, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
 	/** Converts the given byte array to a hex character array. */
 	public static char[] toHexChars(byte[] bytes) {
 		char[] hex = new char[bytes.length * 2];