diff --git a/api/net/sf/briar/api/serial/ObjectReader.java b/api/net/sf/briar/api/serial/ObjectReader.java
deleted file mode 100644
index 012f5f800aae92025570e16520fc8b38e81d5a16..0000000000000000000000000000000000000000
--- a/api/net/sf/briar/api/serial/ObjectReader.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.sf.briar.api.serial;
-
-import java.io.IOException;
-
-public interface ObjectReader<T> {
-
-	T readObject(Reader r) throws IOException;
-}
diff --git a/api/net/sf/briar/api/serial/Reader.java b/api/net/sf/briar/api/serial/Reader.java
index 9dece9845fcdbfd70a34a92fd78ee4b3165b015b..e0610feb838b4fe14e5031e69f1241b29a7d5a04 100644
--- a/api/net/sf/briar/api/serial/Reader.java
+++ b/api/net/sf/briar/api/serial/Reader.java
@@ -18,8 +18,8 @@ public interface Reader {
 	void addConsumer(Consumer c);
 	void removeConsumer(Consumer c);
 
-	void addObjectReader(int id, ObjectReader<?> o);
-	void removeObjectReader(int id);
+	void addStructReader(int id, StructReader<?> o);
+	void removeStructReader(int id);
 
 	boolean hasBoolean() throws IOException;
 	boolean readBoolean() throws IOException;
@@ -51,7 +51,6 @@ public interface Reader {
 	byte[] readBytes(int maxLength) throws IOException;
 
 	boolean hasList() throws IOException;
-	List<Object> readList() throws IOException;
 	<E> List<E> readList(Class<E> e) throws IOException;
 	boolean hasListStart() throws IOException;
 	void readListStart() throws IOException;
@@ -59,7 +58,6 @@ public interface Reader {
 	void readListEnd() throws IOException;
 
 	boolean hasMap() throws IOException;
-	Map<Object, Object> readMap() throws IOException;
 	<K, V> Map<K, V> readMap(Class<K> k, Class<V> v) throws IOException;
 	boolean hasMapStart() throws IOException;
 	void readMapStart() throws IOException;
diff --git a/api/net/sf/briar/api/serial/StructReader.java b/api/net/sf/briar/api/serial/StructReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf512caacfe99c9016c210bee09549b21301ec92
--- /dev/null
+++ b/api/net/sf/briar/api/serial/StructReader.java
@@ -0,0 +1,8 @@
+package net.sf.briar.api.serial;
+
+import java.io.IOException;
+
+public interface StructReader<T> {
+
+	T readStruct(Reader r) throws IOException;
+}
diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java
index 0e89c6bcb1d1588b0ddd19fb1f1edd3d5e9f3473..0b614d7c1bf1e68c437d649ed1994c1a69729106 100644
--- a/components/net/sf/briar/protocol/AckReader.java
+++ b/components/net/sf/briar/protocol/AckReader.java
@@ -16,10 +16,10 @@ import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class AckReader implements ObjectReader<Ack> {
+class AckReader implements StructReader<Ack> {
 
 	private final PacketFactory packetFactory;
 
@@ -27,7 +27,7 @@ class AckReader implements ObjectReader<Ack> {
 		this.packetFactory = packetFactory;
 	}
 
-	public Ack readObject(Reader r) throws IOException {
+	public Ack readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		// Read the data
diff --git a/components/net/sf/briar/protocol/AuthorReader.java b/components/net/sf/briar/protocol/AuthorReader.java
index c59a1028d20faa95b621d569847e331366ebface..a54b78bb357a2618779f43705cb33c4cf8bc8a47 100644
--- a/components/net/sf/briar/protocol/AuthorReader.java
+++ b/components/net/sf/briar/protocol/AuthorReader.java
@@ -10,10 +10,10 @@ import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.ProtocolConstants;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.serial.DigestingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class AuthorReader implements ObjectReader<Author> {
+class AuthorReader implements StructReader<Author> {
 
 	private final MessageDigest messageDigest;
 	private final AuthorFactory authorFactory;
@@ -23,7 +23,7 @@ class AuthorReader implements ObjectReader<Author> {
 		this.authorFactory = authorFactory;
 	}
 
-	public Author readObject(Reader r) throws IOException {
+	public Author readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		// Read and digest the data
diff --git a/components/net/sf/briar/protocol/BatchReader.java b/components/net/sf/briar/protocol/BatchReader.java
index 55031796a11fa1c1e225b4eb353372106364a9ee..e650ac9bb3a6121ab52e2bb19c9418339968b323 100644
--- a/components/net/sf/briar/protocol/BatchReader.java
+++ b/components/net/sf/briar/protocol/BatchReader.java
@@ -10,29 +10,29 @@ import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UnverifiedBatch;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class BatchReader implements ObjectReader<UnverifiedBatch> {
+class BatchReader implements StructReader<UnverifiedBatch> {
 
-	private final ObjectReader<UnverifiedMessage> messageReader;
+	private final StructReader<UnverifiedMessage> messageReader;
 	private final UnverifiedBatchFactory batchFactory;
 
-	BatchReader(ObjectReader<UnverifiedMessage> messageReader,
+	BatchReader(StructReader<UnverifiedMessage> messageReader,
 			UnverifiedBatchFactory batchFactory) {
 		this.messageReader = messageReader;
 		this.batchFactory = batchFactory;
 	}
 
-	public UnverifiedBatch readObject(Reader r) throws IOException {
+	public UnverifiedBatch readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		// Read the data
 		r.addConsumer(counting);
 		r.readStructId(Types.BATCH);
-		r.addObjectReader(Types.MESSAGE, messageReader);
+		r.addStructReader(Types.MESSAGE, messageReader);
 		List<UnverifiedMessage> messages = r.readList(UnverifiedMessage.class);
-		r.removeObjectReader(Types.MESSAGE);
+		r.removeStructReader(Types.MESSAGE);
 		r.removeConsumer(counting);
 		if(messages.isEmpty()) throw new FormatException();
 		// Build and return the batch
diff --git a/components/net/sf/briar/protocol/GroupReader.java b/components/net/sf/briar/protocol/GroupReader.java
index 74f24c36eb7460ef86a45475897285f511a15f7c..e5ac9872171600adc3a7d31ae5076e50c0f85599 100644
--- a/components/net/sf/briar/protocol/GroupReader.java
+++ b/components/net/sf/briar/protocol/GroupReader.java
@@ -10,10 +10,10 @@ import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.ProtocolConstants;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.serial.DigestingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class GroupReader implements ObjectReader<Group> {
+class GroupReader implements StructReader<Group> {
 
 	private final MessageDigest messageDigest;
 	private final GroupFactory groupFactory;
@@ -23,7 +23,7 @@ class GroupReader implements ObjectReader<Group> {
 		this.groupFactory = groupFactory;
 	}
 
-	public Group readObject(Reader r) throws IOException {
+	public Group readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
 		// Read and digest the data
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
index 7a4ddc22f9ff5bef6c1b38405393dad77d35fe36..cd6280190411b6c48b95f73abef6d3226d3c999c 100644
--- a/components/net/sf/briar/protocol/MessageReader.java
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -16,21 +16,21 @@ import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.CopyingConsumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class MessageReader implements ObjectReader<UnverifiedMessage> {
+class MessageReader implements StructReader<UnverifiedMessage> {
 
-	private final ObjectReader<Group> groupReader;
-	private final ObjectReader<Author> authorReader;
+	private final StructReader<Group> groupReader;
+	private final StructReader<Author> authorReader;
 
-	MessageReader(ObjectReader<Group> groupReader,
-			ObjectReader<Author> authorReader) {
+	MessageReader(StructReader<Group> groupReader,
+			StructReader<Author> authorReader) {
 		this.groupReader = groupReader;
 		this.authorReader = authorReader;
 	}
 
-	public UnverifiedMessage readObject(Reader r) throws IOException {
+	public UnverifiedMessage readStruct(Reader r) throws IOException {
 		CopyingConsumer copying = new CopyingConsumer();
 		CountingConsumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		r.addConsumer(copying);
@@ -51,18 +51,18 @@ class MessageReader implements ObjectReader<UnverifiedMessage> {
 		if(r.hasNull()) {
 			r.readNull();
 		} else {
-			r.addObjectReader(Types.GROUP, groupReader);
+			r.addStructReader(Types.GROUP, groupReader);
 			group = r.readStruct(Types.GROUP, Group.class);
-			r.removeObjectReader(Types.GROUP);
+			r.removeStructReader(Types.GROUP);
 		}
 		// Read the author, if there is one
 		Author author = null;
 		if(r.hasNull()) {
 			r.readNull();
 		} else {
-			r.addObjectReader(Types.AUTHOR, authorReader);
+			r.addStructReader(Types.AUTHOR, authorReader);
 			author = r.readStruct(Types.AUTHOR, Author.class);
-			r.removeObjectReader(Types.AUTHOR);
+			r.removeStructReader(Types.AUTHOR);
 		}
 		// Read the subject
 		String subject = r.readString(MAX_SUBJECT_LENGTH);
diff --git a/components/net/sf/briar/protocol/OfferReader.java b/components/net/sf/briar/protocol/OfferReader.java
index 48e3edf5ed311e67f7b06274fc29dc3e9b5cd1ea..b5c2ff099f11cbf8c03a3fb57691866b01c3583b 100644
--- a/components/net/sf/briar/protocol/OfferReader.java
+++ b/components/net/sf/briar/protocol/OfferReader.java
@@ -16,10 +16,10 @@ import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class OfferReader implements ObjectReader<Offer> {
+class OfferReader implements StructReader<Offer> {
 
 	private final PacketFactory packetFactory;
 
@@ -27,7 +27,7 @@ class OfferReader implements ObjectReader<Offer> {
 		this.packetFactory = packetFactory;
 	}
 
-	public Offer readObject(Reader r) throws IOException {
+	public Offer readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		// Read the data
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index aec69bdc6337bd66595a9d5849085498f0a8a703..6b35262237942a281764028d219411627cccb878 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -18,7 +18,7 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.TransportUpdate;
 import net.sf.briar.api.protocol.UnverifiedBatch;
 import net.sf.briar.api.protocol.VerificationExecutor;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.util.BoundedExecutor;
 
 import com.google.inject.AbstractModule;
@@ -58,54 +58,54 @@ public class ProtocolModule extends AbstractModule {
 	}
 
 	@Provides
-	ObjectReader<Ack> getAckReader(PacketFactory ackFactory) {
+	StructReader<Ack> getAckReader(PacketFactory ackFactory) {
 		return new AckReader(ackFactory);
 	}
 
 	@Provides
-	ObjectReader<Author> getAuthorReader(CryptoComponent crypto,
+	StructReader<Author> getAuthorReader(CryptoComponent crypto,
 			AuthorFactory authorFactory) {
 		return new AuthorReader(crypto, authorFactory);
 	}
 
 	@Provides
-	ObjectReader<UnverifiedBatch> getBatchReader(
-			ObjectReader<UnverifiedMessage> messageReader,
+	StructReader<UnverifiedBatch> getBatchReader(
+			StructReader<UnverifiedMessage> messageReader,
 			UnverifiedBatchFactory batchFactory) {
 		return new BatchReader(messageReader, batchFactory);
 	}
 
 	@Provides
-	ObjectReader<Group> getGroupReader(CryptoComponent crypto,
+	StructReader<Group> getGroupReader(CryptoComponent crypto,
 			GroupFactory groupFactory) {
 		return new GroupReader(crypto, groupFactory);
 	}
 
 	@Provides
-	ObjectReader<UnverifiedMessage> getMessageReader(
-			ObjectReader<Group> groupReader,
-			ObjectReader<Author> authorReader) {
+	StructReader<UnverifiedMessage> getMessageReader(
+			StructReader<Group> groupReader,
+			StructReader<Author> authorReader) {
 		return new MessageReader(groupReader, authorReader);
 	}
 
 	@Provides
-	ObjectReader<Offer> getOfferReader(PacketFactory packetFactory) {
+	StructReader<Offer> getOfferReader(PacketFactory packetFactory) {
 		return new OfferReader(packetFactory);
 	}
 
 	@Provides
-	ObjectReader<Request> getRequestReader(PacketFactory packetFactory) {
+	StructReader<Request> getRequestReader(PacketFactory packetFactory) {
 		return new RequestReader(packetFactory);
 	}
 
 	@Provides
-	ObjectReader<SubscriptionUpdate> getSubscriptionReader(
-			ObjectReader<Group> groupReader, PacketFactory packetFactory) {
+	StructReader<SubscriptionUpdate> getSubscriptionReader(
+			StructReader<Group> groupReader, PacketFactory packetFactory) {
 		return new SubscriptionUpdateReader(groupReader, packetFactory);
 	}
 
 	@Provides
-	ObjectReader<TransportUpdate> getTransportReader(
+	StructReader<TransportUpdate> getTransportReader(
 			PacketFactory packetFactory) {
 		return new TransportUpdateReader(packetFactory);
 	}
diff --git a/components/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java b/components/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java
index e74d6f2aedb0f3f48236487f86d3addcd2fbdc5b..0a2ff033078dbef4b90ca7a7249aa790b9e06c14 100644
--- a/components/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java
+++ b/components/net/sf/briar/protocol/ProtocolReaderFactoryImpl.java
@@ -10,7 +10,7 @@ import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.TransportUpdate;
 import net.sf.briar.api.protocol.UnverifiedBatch;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.ReaderFactory;
 
 import com.google.inject.Inject;
@@ -19,21 +19,21 @@ import com.google.inject.Provider;
 class ProtocolReaderFactoryImpl implements ProtocolReaderFactory {
 
 	private final ReaderFactory readerFactory;
-	private final Provider<ObjectReader<Ack>> ackProvider;
-	private final Provider<ObjectReader<UnverifiedBatch>> batchProvider;
-	private final Provider<ObjectReader<Offer>> offerProvider;
-	private final Provider<ObjectReader<Request>> requestProvider;
-	private final Provider<ObjectReader<SubscriptionUpdate>> subscriptionProvider;
-	private final Provider<ObjectReader<TransportUpdate>> transportProvider;
+	private final Provider<StructReader<Ack>> ackProvider;
+	private final Provider<StructReader<UnverifiedBatch>> batchProvider;
+	private final Provider<StructReader<Offer>> offerProvider;
+	private final Provider<StructReader<Request>> requestProvider;
+	private final Provider<StructReader<SubscriptionUpdate>> subscriptionProvider;
+	private final Provider<StructReader<TransportUpdate>> transportProvider;
 
 	@Inject
 	ProtocolReaderFactoryImpl(ReaderFactory readerFactory,
-			Provider<ObjectReader<Ack>> ackProvider,
-			Provider<ObjectReader<UnverifiedBatch>> batchProvider,
-			Provider<ObjectReader<Offer>> offerProvider,
-			Provider<ObjectReader<Request>> requestProvider,
-			Provider<ObjectReader<SubscriptionUpdate>> subscriptionProvider,
-			Provider<ObjectReader<TransportUpdate>> transportProvider) {
+			Provider<StructReader<Ack>> ackProvider,
+			Provider<StructReader<UnverifiedBatch>> batchProvider,
+			Provider<StructReader<Offer>> offerProvider,
+			Provider<StructReader<Request>> requestProvider,
+			Provider<StructReader<SubscriptionUpdate>> subscriptionProvider,
+			Provider<StructReader<TransportUpdate>> transportProvider) {
 		this.readerFactory = readerFactory;
 		this.ackProvider = ackProvider;
 		this.batchProvider = batchProvider;
diff --git a/components/net/sf/briar/protocol/ProtocolReaderImpl.java b/components/net/sf/briar/protocol/ProtocolReaderImpl.java
index a5563613418a44a718479ab9d5d783b239d52dc8..cf6ff211f47d993b57d81cdd79acc3892ecfcc96 100644
--- a/components/net/sf/briar/protocol/ProtocolReaderImpl.java
+++ b/components/net/sf/briar/protocol/ProtocolReaderImpl.java
@@ -11,7 +11,7 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.TransportUpdate;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UnverifiedBatch;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
 
@@ -20,19 +20,19 @@ class ProtocolReaderImpl implements ProtocolReader {
 	private final Reader reader;
 
 	ProtocolReaderImpl(InputStream in, ReaderFactory readerFactory,
-			ObjectReader<Ack> ackReader,
-			ObjectReader<UnverifiedBatch> batchReader,
-			ObjectReader<Offer> offerReader,
-			ObjectReader<Request> requestReader,
-			ObjectReader<SubscriptionUpdate> subscriptionReader,
-			ObjectReader<TransportUpdate> transportReader) {
+			StructReader<Ack> ackReader,
+			StructReader<UnverifiedBatch> batchReader,
+			StructReader<Offer> offerReader,
+			StructReader<Request> requestReader,
+			StructReader<SubscriptionUpdate> subscriptionReader,
+			StructReader<TransportUpdate> transportReader) {
 		reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.ACK, ackReader);
-		reader.addObjectReader(Types.BATCH, batchReader);
-		reader.addObjectReader(Types.OFFER, offerReader);
-		reader.addObjectReader(Types.REQUEST, requestReader);
-		reader.addObjectReader(Types.SUBSCRIPTION_UPDATE, subscriptionReader);
-		reader.addObjectReader(Types.TRANSPORT_UPDATE, transportReader);
+		reader.addStructReader(Types.ACK, ackReader);
+		reader.addStructReader(Types.BATCH, batchReader);
+		reader.addStructReader(Types.OFFER, offerReader);
+		reader.addStructReader(Types.REQUEST, requestReader);
+		reader.addStructReader(Types.SUBSCRIPTION_UPDATE, subscriptionReader);
+		reader.addStructReader(Types.TRANSPORT_UPDATE, transportReader);
 	}
 
 	public boolean eof() throws IOException {
diff --git a/components/net/sf/briar/protocol/RequestReader.java b/components/net/sf/briar/protocol/RequestReader.java
index 005bc0124e596ddb3cc44aaf12083c90ed76d358..b8e657457036a932d34ea5520b15dc64ecb2f413 100644
--- a/components/net/sf/briar/protocol/RequestReader.java
+++ b/components/net/sf/briar/protocol/RequestReader.java
@@ -11,10 +11,10 @@ import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class RequestReader implements ObjectReader<Request> {
+class RequestReader implements StructReader<Request> {
 
 	private final PacketFactory packetFactory;
 
@@ -22,7 +22,7 @@ class RequestReader implements ObjectReader<Request> {
 		this.packetFactory = packetFactory;
 	}
 
-	public Request readObject(Reader r) throws IOException {
+	public Request readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		// Read the data
diff --git a/components/net/sf/briar/protocol/SubscriptionUpdateReader.java b/components/net/sf/briar/protocol/SubscriptionUpdateReader.java
index 2e2dd26543290c2c753d87ae293f7ed9fd2a39de..83a5e55b662228ad1d6a368b9465f4d1cc207017 100644
--- a/components/net/sf/briar/protocol/SubscriptionUpdateReader.java
+++ b/components/net/sf/briar/protocol/SubscriptionUpdateReader.java
@@ -12,29 +12,29 @@ import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class SubscriptionUpdateReader implements ObjectReader<SubscriptionUpdate> {
+class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
 
-	private final ObjectReader<Group> groupReader;
+	private final StructReader<Group> groupReader;
 	private final PacketFactory packetFactory;
 
-	SubscriptionUpdateReader(ObjectReader<Group> groupReader,
+	SubscriptionUpdateReader(StructReader<Group> groupReader,
 			PacketFactory packetFactory) {
 		this.groupReader = groupReader;
 		this.packetFactory = packetFactory;
 	}
 
-	public SubscriptionUpdate readObject(Reader r) throws IOException {
+	public SubscriptionUpdate readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		// Read the data
 		r.addConsumer(counting);
 		r.readStructId(Types.SUBSCRIPTION_UPDATE);
-		r.addObjectReader(Types.GROUP, groupReader);
+		r.addStructReader(Types.GROUP, groupReader);
 		Map<Group, Long> subs = r.readMap(Group.class, Long.class);
-		r.removeObjectReader(Types.GROUP);
+		r.removeStructReader(Types.GROUP);
 		long timestamp = r.readInt64();
 		if(timestamp < 0L) throw new FormatException();
 		r.removeConsumer(counting);
diff --git a/components/net/sf/briar/protocol/TransportUpdateReader.java b/components/net/sf/briar/protocol/TransportUpdateReader.java
index bbea4948f7e71e1a62838f708309457e421a4a77..63e6b899c402a258e6baebb3be203bae51cd7648 100644
--- a/components/net/sf/briar/protocol/TransportUpdateReader.java
+++ b/components/net/sf/briar/protocol/TransportUpdateReader.java
@@ -21,28 +21,28 @@ import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.CountingConsumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
-class TransportUpdateReader implements ObjectReader<TransportUpdate> {
+class TransportUpdateReader implements StructReader<TransportUpdate> {
 
 	private final PacketFactory packetFactory;
-	private final ObjectReader<Transport> transportReader;
+	private final StructReader<Transport> transportReader;
 
 	TransportUpdateReader(PacketFactory packetFactory) {
 		this.packetFactory = packetFactory;
 		transportReader = new TransportReader();
 	}
 
-	public TransportUpdate readObject(Reader r) throws IOException {
+	public TransportUpdate readStruct(Reader r) throws IOException {
 		// Initialise the consumer
 		Consumer counting = new CountingConsumer(MAX_PACKET_LENGTH);
 		// Read the data
 		r.addConsumer(counting);
 		r.readStructId(Types.TRANSPORT_UPDATE);
-		r.addObjectReader(Types.TRANSPORT, transportReader);
+		r.addStructReader(Types.TRANSPORT, transportReader);
 		Collection<Transport> transports = r.readList(Transport.class);
-		r.removeObjectReader(Types.TRANSPORT);
+		r.removeStructReader(Types.TRANSPORT);
 		if(transports.size() > MAX_TRANSPORTS) throw new FormatException();
 		long timestamp = r.readInt64();
 		r.removeConsumer(counting);
@@ -57,9 +57,9 @@ class TransportUpdateReader implements ObjectReader<TransportUpdate> {
 		return packetFactory.createTransportUpdate(transports, timestamp);
 	}
 
-	private static class TransportReader implements ObjectReader<Transport> {
+	private static class TransportReader implements StructReader<Transport> {
 
-		public Transport readObject(Reader r) throws IOException {
+		public Transport readStruct(Reader r) throws IOException {
 			r.readStructId(Types.TRANSPORT);
 			// Read the ID
 			byte[] b = r.readBytes(UniqueId.LENGTH);
diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java
index 7e4dc4c192e91e1298cde03280977cede496d789..9fa9994e46bd251f933bf596b3edd25a3c5d38fc 100644
--- a/components/net/sf/briar/serial/ReaderImpl.java
+++ b/components/net/sf/briar/serial/ReaderImpl.java
@@ -12,7 +12,7 @@ import java.util.Map;
 import net.sf.briar.api.Bytes;
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.serial.Consumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 
 // This class is not thread-safe
@@ -23,7 +23,7 @@ class ReaderImpl implements Reader {
 	private final InputStream in;
 	private final Collection<Consumer> consumers = new ArrayList<Consumer>(0);
 
-	private ObjectReader<?>[] objectReaders = new ObjectReader<?>[] {};
+	private StructReader<?>[] structReaders = new StructReader<?>[] {};
 	private boolean hasLookahead = false, eof = false;
 	private byte next, nextNext;
 	private byte[] buf = null;
@@ -98,21 +98,22 @@ class ReaderImpl implements Reader {
 		if(!consumers.remove(c)) throw new IllegalArgumentException();
 	}
 
-	public void addObjectReader(int id, ObjectReader<?> o) {
+	public void addStructReader(int id, StructReader<?> o) {
 		if(id < 0 || id > 255) throw new IllegalArgumentException();
-		if(objectReaders.length < id + 1) {
-			ObjectReader<?>[] newObjectReaders = new ObjectReader<?>[id + 1];
-			System.arraycopy(objectReaders, 0, newObjectReaders, 0,
-					objectReaders.length);
-			objectReaders = newObjectReaders;
+		if(structReaders.length < id + 1) {
+			int len = Math.min(256, Math.max(id + 1, structReaders.length * 2));
+			StructReader<?>[] newStructReaders = new StructReader<?>[len];
+			System.arraycopy(structReaders, 0, newStructReaders, 0,
+					structReaders.length);
+			structReaders = newStructReaders;
 		}
-		objectReaders[id] = o;
+		structReaders[id] = o;
 	}
 
-	public void removeObjectReader(int id) {
-		if(id < 0 || id > objectReaders.length)
+	public void removeStructReader(int id) {
+		if(id < 0 || id > structReaders.length)
 			throw new IllegalArgumentException();
-		objectReaders[id] = null;
+		structReaders[id] = null;
 	}
 
 	public boolean hasBoolean() throws IOException {
@@ -335,10 +336,6 @@ class ReaderImpl implements Reader {
 				|| (next & Tag.SHORT_MASK) == Tag.SHORT_LIST;
 	}
 
-	public List<Object> readList() throws IOException {
-		return readList(Object.class);
-	}
-
 	public <E> List<E> readList(Class<E> e) throws IOException {
 		if(!hasList()) throw new FormatException();
 		consumeLookahead();
@@ -385,8 +382,8 @@ class ReaderImpl implements Reader {
 		if(hasFloat64()) return Double.valueOf(readFloat64());
 		if(hasString()) return readString();
 		if(hasBytes()) return new Bytes(readBytes());
-		if(hasList()) return readList();
-		if(hasMap()) return readMap();
+		if(hasList()) return readList(Object.class);
+		if(hasMap()) return readMap(Object.class, Object.class);
 		if(hasNull()) {
 			readNull();
 			return null;
@@ -462,10 +459,6 @@ class ReaderImpl implements Reader {
 				|| (next & Tag.SHORT_MASK) == Tag.SHORT_MAP;
 	}
 
-	public Map<Object, Object> readMap() throws IOException {
-		return readMap(Object.class, Object.class);
-	}
-
 	public <K, V> Map<K, V> readMap(Class<K> k, Class<V> v)	throws IOException {
 		if(!hasMap()) throw new FormatException();
 		consumeLookahead();
@@ -538,11 +531,11 @@ class ReaderImpl implements Reader {
 
 	public <T> T readStruct(int id, Class<T> t) throws IOException {
 		if(!hasStruct(id)) throw new FormatException();
-		if(id >= objectReaders.length) throw new FormatException();
-		ObjectReader<?> o = objectReaders[id];
-		if(o == null) throw new FormatException();
+		if(id < 0 || id >= structReaders.length) throw new FormatException();
+		StructReader<?> s = structReaders[id];
+		if(s == null) throw new FormatException();
 		try {
-			return t.cast(o.readObject(this));
+			return t.cast(s.readStruct(this));
 		} catch(ClassCastException e) {
 			throw new FormatException();
 		}
diff --git a/test/net/sf/briar/protocol/AckReaderTest.java b/test/net/sf/briar/protocol/AckReaderTest.java
index 255dc60a0697ad132761e3c169f572c5f2c270a0..0220818d76f711a878421394fd1bd55406e7baf3 100644
--- a/test/net/sf/briar/protocol/AckReaderTest.java
+++ b/test/net/sf/briar/protocol/AckReaderTest.java
@@ -49,7 +49,7 @@ public class AckReaderTest extends BriarTestCase {
 		byte[] b = createAck(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.ACK, ackReader);
+		reader.addStructReader(Types.ACK, ackReader);
 
 		try {
 			reader.readStruct(Types.ACK, Ack.class);
@@ -72,7 +72,7 @@ public class AckReaderTest extends BriarTestCase {
 		byte[] b = createAck(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.ACK, ackReader);
+		reader.addStructReader(Types.ACK, ackReader);
 
 		assertEquals(ack, reader.readStruct(Types.ACK, Ack.class));
 		context.assertIsSatisfied();
@@ -86,7 +86,7 @@ public class AckReaderTest extends BriarTestCase {
 		byte[] b = createEmptyAck();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.ACK, ackReader);
+		reader.addStructReader(Types.ACK, ackReader);
 
 		try {
 			reader.readStruct(Types.ACK, Ack.class);
diff --git a/test/net/sf/briar/protocol/BatchReaderTest.java b/test/net/sf/briar/protocol/BatchReaderTest.java
index e2eabf4ce311abdb36aaabc9cd717248f5cf1172..9023f0f771631cfa7c639794dff053520a2843d3 100644
--- a/test/net/sf/briar/protocol/BatchReaderTest.java
+++ b/test/net/sf/briar/protocol/BatchReaderTest.java
@@ -10,7 +10,7 @@ import net.sf.briar.api.FormatException;
 import net.sf.briar.api.protocol.ProtocolConstants;
 import net.sf.briar.api.protocol.Types;
 import net.sf.briar.api.protocol.UnverifiedBatch;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
 import net.sf.briar.api.serial.Writer;
@@ -30,7 +30,7 @@ public class BatchReaderTest extends BriarTestCase {
 	private final WriterFactory writerFactory;
 	private final Mockery context;
 	private final UnverifiedMessage message;
-	private final ObjectReader<UnverifiedMessage> messageReader;
+	private final StructReader<UnverifiedMessage> messageReader;
 
 	public BatchReaderTest() throws Exception {
 		super();
@@ -51,7 +51,7 @@ public class BatchReaderTest extends BriarTestCase {
 		byte[] b = createBatch(ProtocolConstants.MAX_PACKET_LENGTH + 1);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.BATCH, batchReader);
+		reader.addStructReader(Types.BATCH, batchReader);
 
 		try {
 			reader.readStruct(Types.BATCH, UnverifiedBatch.class);
@@ -75,7 +75,7 @@ public class BatchReaderTest extends BriarTestCase {
 		byte[] b = createBatch(ProtocolConstants.MAX_PACKET_LENGTH);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.BATCH, batchReader);
+		reader.addStructReader(Types.BATCH, batchReader);
 
 		assertEquals(batch, reader.readStruct(Types.BATCH,
 				UnverifiedBatch.class));
@@ -91,7 +91,7 @@ public class BatchReaderTest extends BriarTestCase {
 		byte[] b = createEmptyBatch();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.BATCH, batchReader);
+		reader.addStructReader(Types.BATCH, batchReader);
 
 		try {
 			reader.readStruct(Types.BATCH, UnverifiedBatch.class);
@@ -123,9 +123,9 @@ public class BatchReaderTest extends BriarTestCase {
 		return out.toByteArray();
 	}
 
-	private class TestMessageReader implements ObjectReader<UnverifiedMessage> {
+	private class TestMessageReader implements StructReader<UnverifiedMessage> {
 
-		public UnverifiedMessage readObject(Reader r) throws IOException {
+		public UnverifiedMessage readStruct(Reader r) throws IOException {
 			r.readStructId(Types.MESSAGE);
 			r.readBytes();
 			return message;
diff --git a/test/net/sf/briar/protocol/OfferReaderTest.java b/test/net/sf/briar/protocol/OfferReaderTest.java
index 92067e18fe79dc2008f72e1444490f9676833d9a..0afb60804c3efbc2ed6371c27a8c8082c0393aeb 100644
--- a/test/net/sf/briar/protocol/OfferReaderTest.java
+++ b/test/net/sf/briar/protocol/OfferReaderTest.java
@@ -49,7 +49,7 @@ public class OfferReaderTest extends BriarTestCase {
 		byte[] b = createOffer(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.OFFER, offerReader);
+		reader.addStructReader(Types.OFFER, offerReader);
 
 		try {
 			reader.readStruct(Types.OFFER, Offer.class);
@@ -72,7 +72,7 @@ public class OfferReaderTest extends BriarTestCase {
 		byte[] b = createOffer(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.OFFER, offerReader);
+		reader.addStructReader(Types.OFFER, offerReader);
 
 		assertEquals(offer, reader.readStruct(Types.OFFER, Offer.class));
 		context.assertIsSatisfied();
@@ -86,7 +86,7 @@ public class OfferReaderTest extends BriarTestCase {
 		byte[] b = createEmptyOffer();
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.OFFER, offerReader);
+		reader.addStructReader(Types.OFFER, offerReader);
 
 		try {
 			reader.readStruct(Types.OFFER, Offer.class);
diff --git a/test/net/sf/briar/protocol/RequestReaderTest.java b/test/net/sf/briar/protocol/RequestReaderTest.java
index f241d13dac90e457786c5314d16b3a85092a8435..fb45878d5ee53949fe496bda598af784421bffd3 100644
--- a/test/net/sf/briar/protocol/RequestReaderTest.java
+++ b/test/net/sf/briar/protocol/RequestReaderTest.java
@@ -49,7 +49,7 @@ public class RequestReaderTest extends BriarTestCase {
 		byte[] b = createRequest(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.REQUEST, requestReader);
+		reader.addStructReader(Types.REQUEST, requestReader);
 
 		try {
 			reader.readStruct(Types.REQUEST, Request.class);
@@ -72,7 +72,7 @@ public class RequestReaderTest extends BriarTestCase {
 		byte[] b = createRequest(false);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		Reader reader = readerFactory.createReader(in);
-		reader.addObjectReader(Types.REQUEST, requestReader);
+		reader.addStructReader(Types.REQUEST, requestReader);
 
 		assertEquals(request, reader.readStruct(Types.REQUEST,
 				Request.class));
@@ -102,7 +102,7 @@ public class RequestReaderTest extends BriarTestCase {
 			ByteArrayInputStream in = new ByteArrayInputStream(b);
 			Reader reader = readerFactory.createReader(in);
 			RequestReader requestReader = new RequestReader(packetFactory);
-			reader.addObjectReader(Types.REQUEST, requestReader);
+			reader.addStructReader(Types.REQUEST, requestReader);
 			Request r = reader.readStruct(Types.REQUEST, Request.class);
 			BitSet decoded = r.getBitmap();
 			// Check that the decoded BitSet matches the original - we can't
diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java
index 49a7888d27345473340ea917dd3cf6cf7fc9510c..a79cdb56b8661484bea66ae1f1e8664876cd7513 100644
--- a/test/net/sf/briar/serial/ReaderImplTest.java
+++ b/test/net/sf/briar/serial/ReaderImplTest.java
@@ -14,7 +14,7 @@ import net.sf.briar.BriarTestCase;
 import net.sf.briar.api.Bytes;
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.serial.Consumer;
-import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.StructReader;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.util.StringUtils;
 
@@ -373,15 +373,15 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testReadStruct() throws Exception {
 		setContents("C0" + "83666F6F" + "F1" + "FF" + "83666F6F");
-		// Add object readers for two structs
-		r.addObjectReader(0, new ObjectReader<Foo>() {
-			public Foo readObject(Reader r) throws IOException {
+		// Add readers for two structs
+		r.addStructReader(0, new StructReader<Foo>() {
+			public Foo readStruct(Reader r) throws IOException {
 				r.readStructId(0);
 				return new Foo(r.readString());
 			}
 		});
-		r.addObjectReader(255, new ObjectReader<Bar>() {
-			public Bar readObject(Reader r) throws IOException {
+		r.addStructReader(255, new StructReader<Bar>() {
+			public Bar readStruct(Reader r) throws IOException {
 				r.readStructId(255);
 				return new Bar(r.readString());
 			}
@@ -396,15 +396,15 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testReadStructWithConsumer() throws Exception {
 		setContents("C0" + "83666F6F" + "F1" + "FF" + "83666F6F");
-		// Add object readers for two structs
-		r.addObjectReader(0, new ObjectReader<Foo>() {
-			public Foo readObject(Reader r) throws IOException {
+		// Add readers for two structs
+		r.addStructReader(0, new StructReader<Foo>() {
+			public Foo readStruct(Reader r) throws IOException {
 				r.readStructId(0);
 				return new Foo(r.readString());
 			}
 		});
-		r.addObjectReader(255, new ObjectReader<Bar>() {
-			public Bar readObject(Reader r) throws IOException {
+		r.addStructReader(255, new StructReader<Bar>() {
+			public Bar readStruct(Reader r) throws IOException {
 				r.readStructId(255);
 				return new Bar(r.readString());
 			}
@@ -435,7 +435,7 @@ public class ReaderImplTest extends BriarTestCase {
 	public void testUnknownStructIdThrowsFormatException() throws Exception {
 		setContents("C0" + "83666F6F");
 		assertTrue(r.hasStruct(0));
-		// No object reader has been added for struct ID 0
+		// No reader has been added for struct ID 0
 		try {
 			r.readStruct(0, Foo.class);
 			fail();
@@ -445,15 +445,15 @@ public class ReaderImplTest extends BriarTestCase {
 	@Test
 	public void testWrongClassThrowsFormatException() throws Exception {
 		setContents("C0" + "83666F6F");
-		// Add an object reader for struct ID 0, class Foo
-		r.addObjectReader(0, new ObjectReader<Foo>() {
-			public Foo readObject(Reader r) throws IOException {
+		// Add a reader for struct ID 0, class Foo
+		r.addStructReader(0, new StructReader<Foo>() {
+			public Foo readStruct(Reader r) throws IOException {
 				r.readStructId(0);
 				return new Foo(r.readString());
 			}
 		});
 		assertTrue(r.hasStruct(0));
-		// Trying to read the object as class Bar should throw a FormatException
+		// Trying to read the struct as class Bar should throw a FormatException
 		try {
 			r.readStruct(0, Bar.class);
 			fail();
@@ -461,38 +461,38 @@ public class ReaderImplTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testReadListUsingObjectReader() throws Exception {
+	public void testReadListUsingStructReader() throws Exception {
 		setContents("A" + "1" + "C0" + "83666F6F");
-		// Add an object reader for a struct
-		r.addObjectReader(0, new ObjectReader<Foo>() {
-			public Foo readObject(Reader r) throws IOException {
+		// Add a reader for a struct
+		r.addStructReader(0, new StructReader<Foo>() {
+			public Foo readStruct(Reader r) throws IOException {
 				r.readStructId(0);
 				return new Foo(r.readString());
 			}
 		});
-		// Check that the object reader is used for lists
+		// Check that the reader is used for lists
 		List<Foo> l = r.readList(Foo.class);
 		assertEquals(1, l.size());
 		assertEquals("foo", l.get(0).s);
 	}
 
 	@Test
-	public void testReadMapUsingObjectReader() throws Exception {
+	public void testReadMapUsingStructReader() throws Exception {
 		setContents("B" + "1" + "C0" + "83666F6F" + "C1" + "83626172");
-		// Add object readers for two structs
-		r.addObjectReader(0, new ObjectReader<Foo>() {
-			public Foo readObject(Reader r) throws IOException {
+		// Add readers for two structs
+		r.addStructReader(0, new StructReader<Foo>() {
+			public Foo readStruct(Reader r) throws IOException {
 				r.readStructId(0);
 				return new Foo(r.readString());
 			}
 		});
-		r.addObjectReader(1, new ObjectReader<Bar>() {
-			public Bar readObject(Reader r) throws IOException {
+		r.addStructReader(1, new StructReader<Bar>() {
+			public Bar readStruct(Reader r) throws IOException {
 				r.readStructId(1);
 				return new Bar(r.readString());
 			}
 		});
-		// Check that the object readers are used for maps
+		// Check that the readers are used for maps
 		Map<Foo, Bar> m = r.readMap(Foo.class, Bar.class);
 		assertEquals(1, m.size());
 		Entry<Foo, Bar> e = m.entrySet().iterator().next();