diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index 90e9a5e659b2443f4e5330bfef568a2ad3c83a96..fff0e963fa1f212260c2f1f772ec33f12e28f173 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -13,6 +13,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -461,7 +462,7 @@ DatabaseCleaner.Callback {
 
 	public boolean generateBatch(ContactId c, BatchWriter b) throws DbException,
 	IOException {
-		Collection<MessageId> ids = new ArrayList<MessageId>();
+		Collection<MessageId> ids;
 		Collection<Bytes> messages = new ArrayList<Bytes>();
 		// Get some sendable messages from the database
 		contactLock.readLock().lock();
@@ -593,7 +594,8 @@ DatabaseCleaner.Callback {
 
 	public Collection<MessageId> generateOffer(ContactId c, OfferWriter o)
 	throws DbException, IOException {
-		Collection<MessageId> sendable, sent = new ArrayList<MessageId>();
+		Collection<MessageId> sendable;
+		List<MessageId> sent = new ArrayList<MessageId>();
 		contactLock.readLock().lock();
 		try {
 			if(!containsContact(c)) throw new NoSuchContactException();
@@ -623,7 +625,7 @@ DatabaseCleaner.Callback {
 			sent.add(m);
 		}
 		if(!sent.isEmpty()) o.finish();
-		return sent;
+		return Collections.unmodifiableList(sent);
 	}
 
 	public void generateSubscriptionUpdate(ContactId c,
@@ -1427,7 +1429,7 @@ DatabaseCleaner.Callback {
 
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 	throws DbException {
-		Collection<ContactId> affected;
+		List<ContactId> affected;
 		contactLock.readLock().lock();
 		try {
 			subscriptionLock.writeLock().lock();
@@ -1464,8 +1466,10 @@ DatabaseCleaner.Callback {
 			contactLock.readLock().unlock();
 		}
 		// Call the listeners outside the lock
-		if(!affected.isEmpty())
+		if(!affected.isEmpty()) {
+			affected = Collections.unmodifiableList(affected);
 			callListeners(new SubscriptionsUpdatedEvent(affected));
+		}
 	}
 
 	public void subscribe(Group g) throws DbException {
diff --git a/components/net/sf/briar/i18n/FontManagerImpl.java b/components/net/sf/briar/i18n/FontManagerImpl.java
index 9ae1aa88ddbf2d855ac3eb0b33214d5b230bcbf0..16b6ffccf065e2c711b1c5ec0c6ae016a8485432 100644
--- a/components/net/sf/briar/i18n/FontManagerImpl.java
+++ b/components/net/sf/briar/i18n/FontManagerImpl.java
@@ -35,7 +35,8 @@ public class FontManagerImpl implements FontManager {
 		// Use Padauk for Burmese
 		new BundledFont("Padauk.ttf", 14f, new String[] { "my" }),
 		// Use DroidSansFallback for Chinese, Japanese and Korean
-		new BundledFont("DroidSansFallback.ttf", 12f, new String[] { "zh" , "ja", "ko" }),
+		new BundledFont("DroidSansFallback.ttf", 12f,
+				new String[] { "zh" , "ja", "ko" })
 	};
 
 	// Map from languages to fonts
diff --git a/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
index b2057d89172edc8b3edaf3f98b72961eb0a88b86..1949213209ff06b7400324623a70456294ea18f9 100644
--- a/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
+++ b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
@@ -1,5 +1,6 @@
 package net.sf.briar.lifecycle;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -38,10 +39,11 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
 
 	WindowsShutdownManagerImpl() {
 		// Use the Unicode versions of Win32 API calls
-		options = new HashMap<String, Object>();
-		options.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
-		options.put(Library.OPTION_FUNCTION_MAPPER,
+		Map<String, Object> m = new HashMap<String, Object>();
+		m.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
+		m.put(Library.OPTION_FUNCTION_MAPPER,
 				W32APIFunctionMapper.UNICODE);
+		options = Collections.unmodifiableMap(m);
 	}
 
 	@Override
diff --git a/components/net/sf/briar/plugins/PluginManagerImpl.java b/components/net/sf/briar/plugins/PluginManagerImpl.java
index 3f6de4a0ff98e5b243ea884c0ebe8736bd6afd93..391ce8b189786129878fe5c89b822ecf8aca81ba 100644
--- a/components/net/sf/briar/plugins/PluginManagerImpl.java
+++ b/components/net/sf/briar/plugins/PluginManagerImpl.java
@@ -55,8 +55,8 @@ class PluginManagerImpl implements PluginManager {
 	private final Poller poller;
 	private final ConnectionDispatcher dispatcher;
 	private final UiCallback uiCallback;
-	private final List<BatchPlugin> batchPlugins;
-	private final List<StreamPlugin> streamPlugins;
+	private final List<BatchPlugin> batchPlugins; // Locking: this
+	private final List<StreamPlugin> streamPlugins; // Locking: this
 
 	@Inject
 	PluginManagerImpl(DatabaseComponent db, Executor executor, Poller poller,
@@ -156,7 +156,7 @@ class PluginManagerImpl implements PluginManager {
 		List<Plugin> plugins = new ArrayList<Plugin>();
 		plugins.addAll(batchPlugins);
 		plugins.addAll(streamPlugins);
-		poller.startPolling(plugins);
+		poller.startPolling(Collections.unmodifiableList(plugins));
 		// Return the number of plugins successfully started
 		return batchPlugins.size() + streamPlugins.size();
 	}
diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
index e4d2b92633ec5e3c7e6d87c7e6930186cf1b2e27..ff1bccd6e0ad1ff754672888b18dabc7409fb158 100644
--- a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
+++ b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
@@ -241,7 +241,8 @@ class BluetoothPlugin extends AbstractPlugin implements StreamPlugin {
 			}
 		}
 		ContactListener listener = new ContactListener(discoveryAgent,
-				addresses, uuids);
+				Collections.unmodifiableMap(addresses),
+				Collections.unmodifiableMap(uuids));
 		synchronized(discoveryLock) {
 			try {
 				discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
diff --git a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
index 0d582bc3505d3475981a10c483b870269214e907..577449bdfa0e00ae169b2dbd8dacd22a5b2fe9fd 100644
--- a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
@@ -2,7 +2,7 @@ package net.sf.briar.plugins.file;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.List;
+import java.util.Collection;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -47,7 +47,7 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 
 	public void run() {
 		try {
-			List<File> drives = finder.findRemovableDrives();
+			Collection<File> drives = finder.findRemovableDrives();
 			while(running) {
 				synchronized(pollingLock) {
 					try {
@@ -58,7 +58,7 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 					}
 				}
 				if(!running) return;
-				List<File> newDrives = finder.findRemovableDrives();
+				Collection<File> newDrives = finder.findRemovableDrives();
 				for(File f : newDrives) {
 					if(!drives.contains(f)) callback.driveInserted(f);
 				}
diff --git a/components/net/sf/briar/plugins/file/RemovableDriveFinder.java b/components/net/sf/briar/plugins/file/RemovableDriveFinder.java
index 46157a71c01753e8c084653c0af457bf874160cb..25bdd5c28ea73fd4c49ccbaa2c174ee2ab475c2c 100644
--- a/components/net/sf/briar/plugins/file/RemovableDriveFinder.java
+++ b/components/net/sf/briar/plugins/file/RemovableDriveFinder.java
@@ -2,9 +2,9 @@ package net.sf.briar.plugins.file;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.List;
+import java.util.Collection;
 
 interface RemovableDriveFinder {
 
-	List<File> findRemovableDrives() throws IOException;
+	Collection<File> findRemovableDrives() throws IOException;
 }
diff --git a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
index af60b0ec87fe3a4ff0f3e98274dbfeb6a3a1c59a..3126e66e013df237fd8586730c8eabdf90e8e428 100644
--- a/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
+++ b/components/net/sf/briar/plugins/file/RemovableDrivePlugin.java
@@ -4,6 +4,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.logging.Level;
@@ -69,7 +70,8 @@ implements RemovableDriveMonitor.Callback {
 	@Override
 	protected File chooseOutputDirectory() {
 		try {
-			List<File> drives = finder.findRemovableDrives();
+			List<File> drives =
+				new ArrayList<File>(finder.findRemovableDrives());
 			if(drives.isEmpty()) return null;
 			String[] paths = new String[drives.size()];
 			for(int i = 0; i < paths.length; i++) {
@@ -96,7 +98,7 @@ implements RemovableDriveMonitor.Callback {
 
 	@Override
 	protected Collection<File> findFilesByName(String filename) {
-		Collection<File> matches = new ArrayList<File>();
+		List<File> matches = new ArrayList<File>();
 		try {
 			for(File drive : finder.findRemovableDrives()) {
 				File[] files = drive.listFiles();
@@ -110,7 +112,7 @@ implements RemovableDriveMonitor.Callback {
 		} catch(IOException e) {
 			if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
 		}
-		return matches;
+		return Collections.unmodifiableList(matches);
 	}
 
 	public void driveInserted(File root) {
diff --git a/components/net/sf/briar/plugins/file/UnixRemovableDriveFinder.java b/components/net/sf/briar/plugins/file/UnixRemovableDriveFinder.java
index 0b5cac4014703548aa8030e5a6dd0ac458f9e94a..4c636d0ba9d0f5b1b7ea32fbc17284c8a1168e77 100644
--- a/components/net/sf/briar/plugins/file/UnixRemovableDriveFinder.java
+++ b/components/net/sf/briar/plugins/file/UnixRemovableDriveFinder.java
@@ -3,6 +3,7 @@ package net.sf.briar.plugins.file;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Scanner;
 
@@ -34,6 +35,6 @@ abstract class UnixRemovableDriveFinder implements RemovableDriveFinder {
 		} finally {
 			s.close();
 		}
-		return drives;
+		return Collections.unmodifiableList(drives);
 	}
 }
diff --git a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
index c4daf247094c9c0d2fd3fe9fcdcdef41e537e080..18941b9a9ae7c23e9231709a344e0b06296ec66b 100644
--- a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
@@ -11,10 +11,11 @@ import net.contentobjects.jnotify.JNotifyListener;
 abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
 JNotifyListener {
 
+	// Locking: this
 	private final List<Integer> watches = new ArrayList<Integer>();
 
-	private boolean started = false;
-	private Callback callback = null;
+	private boolean started = false; // Locking: this
+	private Callback callback = null; // Locking: this
 
 	protected abstract String[] getPathsToWatch();
 
@@ -37,11 +38,9 @@ JNotifyListener {
 		watches.clear();
 	}
 
-	public void fileCreated(int wd, String rootPath, String name) {
-		synchronized(this) {
-			if(!started) throw new IllegalStateException();
-			callback.driveInserted(new File(rootPath + "/" + name));
-		}
+	public synchronized void fileCreated(int wd, String rootPath, String name) {
+		if(!started) throw new IllegalStateException();
+		callback.driveInserted(new File(rootPath + "/" + name));
 	}
 
 	public void fileDeleted(int wd, String rootPath, String name) {
diff --git a/components/net/sf/briar/plugins/file/WindowsRemovableDriveFinder.java b/components/net/sf/briar/plugins/file/WindowsRemovableDriveFinder.java
index 78c2dfab14684c668fd10b954033eaaf4e85d32e..7672cae66d3bd677907e82013611bf931301f6a1 100644
--- a/components/net/sf/briar/plugins/file/WindowsRemovableDriveFinder.java
+++ b/components/net/sf/briar/plugins/file/WindowsRemovableDriveFinder.java
@@ -3,6 +3,8 @@ package net.sf.briar.plugins.file;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import com.sun.jna.platform.win32.Kernel32;
@@ -12,7 +14,7 @@ class WindowsRemovableDriveFinder implements RemovableDriveFinder {
 	// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364939.aspx
 	private static final int DRIVE_REMOVABLE = 2;
 
-	public List<File> findRemovableDrives() throws IOException {
+	public Collection<File> findRemovableDrives() throws IOException {
 		File[] roots = File.listRoots();
 		if(roots == null) throw new IOException();
 		List<File> drives = new ArrayList<File>();
@@ -24,7 +26,6 @@ class WindowsRemovableDriveFinder implements RemovableDriveFinder {
 				throw new IOException(e.getMessage());
 			}
 		}
-		return drives;
+		return Collections.unmodifiableList(drives);
 	}
-
 }
diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java
index 90d5529b7359655277bd300bc503caf18069de06..afb92a23ce7f79883315a5b0523b0f4d6dbf1072 100644
--- a/components/net/sf/briar/serial/ReaderImpl.java
+++ b/components/net/sf/briar/serial/ReaderImpl.java
@@ -3,6 +3,7 @@ package net.sf.briar.serial;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -14,12 +15,13 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
+// This class is not thread-safe
 class ReaderImpl implements Reader {
 
 	private static final byte[] EMPTY_BUFFER = new byte[] {};
 
 	private final InputStream in;
-	private final List<Consumer> consumers = new ArrayList<Consumer>(0);
+	private final Collection<Consumer> consumers = new ArrayList<Consumer>(0);
 
 	private ObjectReader<?>[] objectReaders = new ObjectReader<?>[] {};
 	private boolean hasLookahead = false, eof = false;
@@ -346,7 +348,7 @@ class ReaderImpl implements Reader {
 			List<E> list = new ArrayList<E>();
 			while(!hasEnd()) list.add(readObject(e));
 			readEnd();
-			return list;
+			return Collections.unmodifiableList(list);
 		} else {
 			int length = 0xFF & next ^ Tag.SHORT_LIST;
 			return readList(e, length);
@@ -358,7 +360,7 @@ class ReaderImpl implements Reader {
 		if(length == 0) return Collections.emptyList();
 		List<E> list = new ArrayList<E>();
 		for(int i = 0; i < length; i++) list.add(readObject(e));
-		return list;
+		return Collections.unmodifiableList(list);
 	}
 
 	private boolean hasEnd() throws IOException {
@@ -478,7 +480,7 @@ class ReaderImpl implements Reader {
 					throw new FormatException(); // Duplicate key
 			}
 			readEnd();
-			return m;
+			return Collections.unmodifiableMap(m);
 		} else {
 			int size = 0xFF & next ^ Tag.SHORT_MAP;
 			return readMap(k, v, size);
@@ -494,7 +496,7 @@ class ReaderImpl implements Reader {
 			if(m.put(readObject(k), readObject(v)) != null)
 				throw new FormatException(); // Duplicate key
 		}
-		return m;
+		return Collections.unmodifiableMap(m);
 	}
 
 	public boolean hasMapStart() throws IOException {
diff --git a/components/net/sf/briar/serial/WriterImpl.java b/components/net/sf/briar/serial/WriterImpl.java
index a212a13075fe3c2f8c80874ee39fd0dd10d10035..24f51ec9fb7bea876caed82ac3ac332d4914c025 100644
--- a/components/net/sf/briar/serial/WriterImpl.java
+++ b/components/net/sf/briar/serial/WriterImpl.java
@@ -12,10 +12,11 @@ import net.sf.briar.api.Bytes;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.Writer;
 
+// This class is not thread-safe
 class WriterImpl implements Writer {
 
 	private final OutputStream out;
-	private final List<Consumer> consumers = new ArrayList<Consumer>(0);
+	private final Collection<Consumer> consumers = new ArrayList<Consumer>(0);
 
 	WriterImpl(OutputStream out) {
 		this.out = out;
diff --git a/components/net/sf/briar/transport/ConnectionWindowImpl.java b/components/net/sf/briar/transport/ConnectionWindowImpl.java
index adb76fa7b87e346642caa1d9d224aef1e41114b7..8aa1708bc92e1ef7191a7fc0850d2bf1784506f0 100644
--- a/components/net/sf/briar/transport/ConnectionWindowImpl.java
+++ b/components/net/sf/briar/transport/ConnectionWindowImpl.java
@@ -10,6 +10,7 @@ import net.sf.briar.api.protocol.TransportIndex;
 import net.sf.briar.api.transport.ConnectionWindow;
 import net.sf.briar.util.ByteUtils;
 
+// This class is not thread-safe
 class ConnectionWindowImpl implements ConnectionWindow {
 
 	private final CryptoComponent crypto;
diff --git a/components/net/sf/briar/transport/stream/StreamConnection.java b/components/net/sf/briar/transport/stream/StreamConnection.java
index f24557cedf0cb89b0965c8b49220e361d0d1fe2e..18a2c918a94d0bb77093539dc849e5727c9b5122 100644
--- a/components/net/sf/briar/transport/stream/StreamConnection.java
+++ b/components/net/sf/briar/transport/stream/StreamConnection.java
@@ -6,7 +6,9 @@ import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -58,11 +60,10 @@ abstract class StreamConnection implements DatabaseListener {
 	protected final ContactId contactId;
 	protected final StreamTransportConnection connection;
 
-	// These fields must only be accessed with this's lock held
-	private int writerFlags = 0;
-	private Collection<MessageId> offered = null;
-	private Collection<MessageId> requested = null;
-	private Offer incomingOffer = null;
+	private int writerFlags = 0; // Locking: this
+	private Collection<MessageId> offered = null; // Locking: this
+	private LinkedList<MessageId> requested = null; // Locking: this
+	private Offer incomingOffer = null; // Locking: this
 
 	StreamConnection(ConnectionReaderFactory connReaderFactory,
 			ConnectionWriterFactory connWriterFactory, DatabaseComponent db,
@@ -143,15 +144,15 @@ abstract class StreamConnection implements DatabaseListener {
 					}
 					// Work out which messages were requested
 					BitSet b = r.getBitmap();
-					Collection<MessageId> req = new LinkedList<MessageId>();
-					Collection<MessageId> seen = new ArrayList<MessageId>();
+					LinkedList<MessageId> req = new LinkedList<MessageId>();
+					List<MessageId> seen = new ArrayList<MessageId>();
 					int i = 0;
 					for(MessageId m : off) {
 						if(b.get(i++)) req.add(m);
 						else seen.add(m);
 					}
 					// Mark the unrequested messages as seen
-					db.setSeen(contactId, seen);
+					db.setSeen(contactId, Collections.unmodifiableList(seen));
 					// Store the requested message IDs and notify the writer
 					synchronized(this) {
 						if(requested != null)