diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
index eafa66098ca47026d91cf04be40e47c756145e49..81b41f99fed3dbc19467fd6ce6e56dc7c6067ee4 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
@@ -34,7 +34,7 @@ import static android.widget.Toast.LENGTH_SHORT;
 import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 
 public class ForumActivity extends
-		ThreadListActivity<Forum, ForumEntry, ForumPostHeader, NestedForumAdapter> {
+		ThreadListActivity<Forum, ForumItem, ForumPostHeader, NestedForumAdapter> {
 
 	private static final int REQUEST_FORUM_SHARED = 3;
 
@@ -47,7 +47,7 @@ public class ForumActivity extends
 	}
 
 	@Override
-	protected ThreadListController<Forum, ForumEntry, ForumPostHeader> getController() {
+	protected ThreadListController<Forum, ForumItem, ForumPostHeader> getController() {
 		return forumController;
 	}
 
diff --git a/briar-android/src/org/briarproject/android/forum/ForumController.java b/briar-android/src/org/briarproject/android/forum/ForumController.java
index 275ccc135e3344272e961a8b133f738061a9be5f..9b1fba5641b032c862b4fb9ba1ffcf254b773fd6 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumController.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumController.java
@@ -1,12 +1,10 @@
 package org.briarproject.android.forum;
 
-import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.android.threaded.ThreadListController;
-import org.briarproject.api.db.DbException;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumPostHeader;
 
 public interface ForumController
-		extends ThreadListController<Forum, ForumEntry, ForumPostHeader> {
+		extends ThreadListController<Forum, ForumItem, ForumPostHeader> {
 
 }
diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
index 9ec21e76cfa49adf1da8d1a7b64171b3b6451821..21a19cad4107f1b150323a6cc0b36c26b6662d10 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
@@ -4,6 +4,7 @@ import android.support.annotation.Nullable;
 
 import org.briarproject.android.api.AndroidNotificationManager;
 import org.briarproject.android.threaded.ThreadListControllerImpl;
+import org.briarproject.api.clients.MessageTracker.GroupCount;
 import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
@@ -14,20 +15,20 @@ import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPost;
 import org.briarproject.api.forum.ForumPostHeader;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.util.StringUtils;
 
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
 public class ForumControllerImpl
-		extends ThreadListControllerImpl<Forum, ForumEntry, ForumPostHeader, ForumPost>
+		extends ThreadListControllerImpl<Forum, ForumItem, ForumPostHeader, ForumPost>
 		implements ForumController {
 
 	private static final Logger LOG =
@@ -37,12 +38,12 @@ public class ForumControllerImpl
 
 	@Inject
 	ForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
-			LifecycleManager lifecycleManager,
+			LifecycleManager lifecycleManager, IdentityManager identityManager,
 			@CryptoExecutor Executor cryptoExecutor,
 			ForumManager forumManager, EventBus eventBus,
 			AndroidNotificationManager notificationManager) {
-		super(dbExecutor, lifecycleManager, cryptoExecutor, eventBus,
-				notificationManager);
+		super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
+				eventBus, notificationManager);
 		this.forumManager = forumManager;
 	}
 
@@ -72,7 +73,7 @@ public class ForumControllerImpl
 	}
 
 	@Override
-	protected Forum loadGroupItem() throws DbException {
+	protected Forum loadNamedGroup() throws DbException {
 		return forumManager.getForum(getGroupId());
 	}
 
@@ -82,18 +83,8 @@ public class ForumControllerImpl
 	}
 
 	@Override
-	protected Map<MessageId, String> loadBodies(
-			Collection<ForumPostHeader> headers)
-			throws DbException {
-		Map<MessageId, String> bodies = new HashMap<>();
-		for (ForumPostHeader header : headers) {
-			if (!bodyCache.containsKey(header.getId())) {
-				String body = StringUtils
-						.fromUtf8(forumManager.getPostBody(header.getId()));
-				bodies.put(header.getId(), body);
-			}
-		}
-		return bodies;
+	protected String loadMessageBody(MessageId id) throws DbException {
+		return StringUtils.fromUtf8(forumManager.getPostBody(id));
 	}
 
 	@Override
@@ -102,9 +93,17 @@ public class ForumControllerImpl
 	}
 
 	@Override
-	protected ForumPost createLocalMessage(String body,
-			@Nullable MessageId parentId) throws DbException {
-		return forumManager.createLocalPost(getGroupId(), body, parentId);
+	protected long getLatestTimestamp() throws DbException {
+		GroupCount count = forumManager.getGroupCount(getGroupId());
+		return count.getLatestMsgTime();
+	}
+
+	@Override
+	protected ForumPost createLocalMessage(String body, long timestamp,
+			@Nullable MessageId parentId, LocalAuthor author) {
+		return forumManager
+				.createLocalPost(getGroupId(), body, timestamp, parentId,
+						author);
 	}
 
 	@Override
@@ -114,13 +113,13 @@ public class ForumControllerImpl
 	}
 
 	@Override
-	protected void deleteGroupItem(Forum forum) throws DbException {
+	protected void deleteNamedGroup(Forum forum) throws DbException {
 		forumManager.removeForum(forum);
 	}
 
 	@Override
-	protected ForumEntry buildItem(ForumPostHeader header, String body) {
-		return new ForumEntry(header, body);
+	protected ForumItem buildItem(ForumPostHeader header, String body) {
+		return new ForumItem(header, body);
 	}
 
 }
diff --git a/briar-android/src/org/briarproject/android/forum/ForumEntry.java b/briar-android/src/org/briarproject/android/forum/ForumItem.java
similarity index 61%
rename from briar-android/src/org/briarproject/android/forum/ForumEntry.java
rename to briar-android/src/org/briarproject/android/forum/ForumItem.java
index 02817e1ab92bd80870b1b1c18cef4c81106c7a3a..80f28e2d3aaf508e8d0581db1e23cb3d3051efbc 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumEntry.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumItem.java
@@ -6,15 +6,17 @@ import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.Author.Status;
 import org.briarproject.api.sync.MessageId;
 
-/* This class is not thread safe */
-public class ForumEntry extends ThreadItem {
+import javax.annotation.concurrent.NotThreadSafe;
 
-	ForumEntry(ForumPostHeader h, String text) {
-		super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(),
+@NotThreadSafe
+public class ForumItem extends ThreadItem {
+
+	ForumItem(ForumPostHeader h, String body) {
+		super(h.getId(), h.getParentId(), body, h.getTimestamp(), h.getAuthor(),
 				h.getAuthorStatus(), h.isRead());
 	}
 
-	public ForumEntry(MessageId messageId, MessageId parentId, String text,
+	public ForumItem(MessageId messageId, MessageId parentId, String text,
 			long timestamp, Author author, Status status) {
 		super(messageId, parentId, text, timestamp, author, status, true);
 	}
diff --git a/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java b/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java
index d1cb2400838d677716d59695f01c84b51b4eddac..f690df90afea7c5222b737d08afd3cfd9372f349 100644
--- a/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java
+++ b/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java
@@ -10,9 +10,9 @@ import org.briarproject.R;
 import org.briarproject.android.threaded.ThreadItemAdapter;
 
 @UiThread
-public class NestedForumAdapter extends ThreadItemAdapter<ForumEntry> {
+public class NestedForumAdapter extends ThreadItemAdapter<ForumItem> {
 
-	public NestedForumAdapter(ThreadItemListener<ForumEntry> listener,
+	public NestedForumAdapter(ThreadItemListener<ForumItem> listener,
 			LinearLayoutManager layoutManager) {
 		super(listener, layoutManager);
 	}
diff --git a/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java b/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java
index 8263ccb4aa11b4eda2ab539639c352b79a8d0724..b73558ff5d668c1adf37f0e8e69a9f971411967c 100644
--- a/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java
+++ b/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java
@@ -4,7 +4,7 @@ import android.view.View;
 
 import org.briarproject.android.threaded.ThreadItemViewHolder;
 
-public class NestedForumHolder extends ThreadItemViewHolder<ForumEntry> {
+public class NestedForumHolder extends ThreadItemViewHolder<ForumItem> {
 
 	public NestedForumHolder(View v) {
 		super(v);
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
index dfadc9d414f846b91f6e29435f9444d19703b10a..87bef06bfe692bdd2331836f1c58c6b0a672fde6 100644
--- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
@@ -4,12 +4,15 @@ import android.support.annotation.Nullable;
 
 import org.briarproject.android.api.AndroidNotificationManager;
 import org.briarproject.android.threaded.ThreadListControllerImpl;
+import org.briarproject.api.clients.MessageTracker.GroupCount;
 import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.GroupMessageAddedEvent;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.privategroup.GroupMessage;
 import org.briarproject.api.privategroup.GroupMessageHeader;
@@ -18,8 +21,6 @@ import org.briarproject.api.privategroup.PrivateGroupManager;
 import org.briarproject.api.sync.MessageId;
 
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -36,19 +37,19 @@ public class GroupControllerImpl
 
 	@Inject
 	GroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
-			LifecycleManager lifecycleManager,
+			LifecycleManager lifecycleManager, IdentityManager identityManager,
 			@CryptoExecutor Executor cryptoExecutor,
 			PrivateGroupManager privateGroupManager, EventBus eventBus,
 			AndroidNotificationManager notificationManager) {
-		super(dbExecutor, lifecycleManager, cryptoExecutor, eventBus,
-				notificationManager);
+		super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
+				eventBus, notificationManager);
 		this.privateGroupManager = privateGroupManager;
 	}
 
 	@Override
 	public void onActivityResume() {
 		super.onActivityResume();
-		notificationManager.clearForumPostNotification(getGroupId());
+		// TODO: Add new notification manager methods for private groups
 	}
 
 	@Override
@@ -56,7 +57,7 @@ public class GroupControllerImpl
 		super.eventOccurred(e);
 
 		if (e instanceof GroupMessageAddedEvent) {
-			final GroupMessageAddedEvent gmae = (GroupMessageAddedEvent) e;
+			GroupMessageAddedEvent gmae = (GroupMessageAddedEvent) e;
 			if (!gmae.isLocal() && gmae.getGroupId().equals(getGroupId())) {
 				LOG.info("Group message received, adding...");
 				final GroupMessageHeader h = gmae.getHeader();
@@ -71,7 +72,7 @@ public class GroupControllerImpl
 	}
 
 	@Override
-	protected PrivateGroup loadGroupItem() throws DbException {
+	protected PrivateGroup loadNamedGroup() throws DbException {
 		return privateGroupManager.getPrivateGroup(getGroupId());
 	}
 
@@ -81,18 +82,8 @@ public class GroupControllerImpl
 	}
 
 	@Override
-	protected Map<MessageId, String> loadBodies(
-			Collection<GroupMessageHeader> headers)
-			throws DbException {
-		Map<MessageId, String> bodies = new HashMap<>();
-		for (GroupMessageHeader header : headers) {
-			if (!bodyCache.containsKey(header.getId())) {
-				String body =
-						privateGroupManager.getMessageBody(header.getId());
-				bodies.put(header.getId(), body);
-			}
-		}
-		return bodies;
+	protected String loadMessageBody(MessageId id) throws DbException {
+		return privateGroupManager.getMessageBody(id);
 	}
 
 	@Override
@@ -101,10 +92,17 @@ public class GroupControllerImpl
 	}
 
 	@Override
-	protected GroupMessage createLocalMessage(String body,
-			@Nullable MessageId parentId) throws DbException {
+	protected long getLatestTimestamp() throws DbException {
+		GroupCount count = privateGroupManager.getGroupCount(getGroupId());
+		return count.getLatestMsgTime();
+	}
+
+	@Override
+	protected GroupMessage createLocalMessage(String body, long timestamp,
+			@Nullable MessageId parentId, LocalAuthor author) {
 		return privateGroupManager
-				.createLocalMessage(getGroupId(), body, parentId);
+				.createLocalMessage(getGroupId(), body, timestamp, parentId,
+						author);
 	}
 
 	@Override
@@ -114,7 +112,7 @@ public class GroupControllerImpl
 	}
 
 	@Override
-	protected void deleteGroupItem(PrivateGroup group) throws DbException {
+	protected void deleteNamedGroup(PrivateGroup group) throws DbException {
 		privateGroupManager.removePrivateGroup(group.getId());
 	}
 
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
index b29fc227b64b347144df3a1abefa829d4844920c..bae48022a7e8599cf4bbeef00f069ee88ed7651c 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
@@ -31,7 +31,7 @@ class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> {
 
 	@Override
 	public void onBindViewHolder(GroupViewHolder ui, int position) {
-		ui.bindView(ctx, getItemAt(position), listener);
+		ui.bindView(ctx, items.get(position), listener);
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java
index 9ad7536eb70f6544a47282797050577ae4be63b7..9db84cdcadfa143748c975dce863815075ba0d1c 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java
@@ -68,6 +68,7 @@ public class GroupListControllerImpl extends DbControllerImpl
 			throw new IllegalStateException(
 					"GroupListListener needs to be attached");
 		eventBus.addListener(this);
+		// TODO: Add new notification manager methods for private groups
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
index 3994a0a1e57d0a93f3d9849c7862e4b9ffe00e4b..898e81fe29730d6320d88280a71cac31276a8b6e 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
@@ -2,7 +2,6 @@ package org.briarproject.android.privategroup.list;
 
 import android.content.Context;
 import android.content.Intent;
-import android.support.annotation.Nullable;
 import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
@@ -51,7 +50,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
 		remove = (Button) v.findViewById(R.id.removeButton);
 	}
 
-	void bindView(final Context ctx, @Nullable final GroupItem group,
+	void bindView(final Context ctx, final GroupItem group,
 			@NotNull final OnGroupRemoveClickListener listener) {
 		if (group == null) return;
 
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java
index 01aae5c68b8f50689b88759e1ba8ee43d71d2a86..129e5348593003ac3e4783b2b43324455ea4ad58 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java
@@ -9,6 +9,7 @@ import android.support.v7.widget.RecyclerView;
 import org.briarproject.api.sync.MessageId;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -56,7 +57,7 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
 		return replyItem;
 	}
 
-	public void setItems(List<I> items) {
+	public void setItems(Collection<I> items) {
 		this.items.clear();
 		this.items.addAll(items);
 		notifyDataSetChanged();
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java
index 27ca9d80dce9ae5f775848387ff55a2f5e236e62..0b5a5ddc5b74cc34bee5bb6d63b2534ce2aa0227 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java
@@ -57,7 +57,7 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
 
 	// TODO improve encapsulation, so we don't need to pass the adapter here
 	public void bind(final ThreadItemAdapter<I> adapter,
-			final ThreadItemListener listener, final I item, int pos) {
+			final ThreadItemListener<I> listener, final I item, int pos) {
 
 		textView.setText(StringUtils.trim(item.getText()));
 
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
index ab480a4bd04f84f0cc51517b6412d6e81518191a..889399525de6ab9a880511dd43789533d65b835d 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
@@ -26,10 +26,9 @@ import org.briarproject.api.clients.PostHeader;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
+import org.briarproject.util.StringUtils;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 
 import static android.support.design.widget.Snackbar.make;
 import static android.view.View.GONE;
@@ -75,7 +74,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 		list.setAdapter(adapter);
 
 		if (state != null) {
-			replyId = new MessageId(state.getByteArray(KEY_REPLY_ID));
+			byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID);
+			if(replyIdBytes != null) replyId = new MessageId(replyIdBytes);
 		}
 
 		loadItems();
@@ -110,9 +110,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 				new UiResultExceptionHandler<Collection<I>, DbException>(
 						this) {
 					@Override
-					public void onResultUi(Collection<I> result) {
-						// FIXME What's the benefit of copying the collection?
-						List<I> items = new ArrayList<>(result);
+					public void onResultUi(Collection<I> items) {
 						if (items.isEmpty()) {
 							list.showData();
 						} else {
@@ -225,7 +223,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 	public void onSendClick(String text) {
 		if (text.trim().length() == 0)
 			return;
-		if (text.length() > getMaxBodyLength()) {
+		if (StringUtils.isTooLong(text, getMaxBodyLength())) {
 			displaySnackbarShort(R.string.text_too_long);
 			return;
 		}
@@ -243,12 +241,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 						finish();
 					}
 				};
-		if (replyItem == null) {
-			// root post
-			getController().send(text, handler);
-		} else {
-			getController().send(text, replyItem.getId(), handler);
-		}
+		getController().createAndStoreMessage(text,
+				replyItem != null ? replyItem.getId() : null, handler);
 		textInput.hideSoftKeyboard();
 		textInput.setVisibility(GONE);
 		adapter.setReplyItem(null);
@@ -268,6 +262,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 					@Override
 					public void onExceptionUi(DbException exception) {
 						// TODO add proper exception handling
+						finish();
 					}
 				});
 	}
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListController.java b/briar-android/src/org/briarproject/android/threaded/ThreadListController.java
index b62b6fd27788a2e7009f8b3a1eb59e443abb80b6..a7731d8d48ed2046f39c962e8781b86c4c9c396d 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadListController.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadListController.java
@@ -29,9 +29,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
 
 	void markItemsRead(Collection<I> items);
 
-	void send(String body, ResultExceptionHandler<I, DbException> handler);
-
-	void send(String body, @Nullable MessageId parentId,
+	void createAndStoreMessage(String body, @Nullable MessageId parentId,
 			ResultExceptionHandler<I, DbException> handler);
 
 	void deleteNamedGroup(ResultExceptionHandler<Void, DbException> handler);
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java
index c412d2eaacfd11c435a13b8623544baf257d017a..02e6bfc66f1bdb7446cd1893d1eb7c6d22398df1 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java
@@ -17,6 +17,8 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.GroupRemovedEvent;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
@@ -40,11 +42,12 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	private static final Logger LOG =
 			Logger.getLogger(ThreadListControllerImpl.class.getName());
 
-	protected final Executor cryptoExecutor;
+	private final IdentityManager identityManager;
+	private final Executor cryptoExecutor;
 	protected final AndroidNotificationManager notificationManager;
 	private final EventBus eventBus;
 
-	protected final Map<MessageId, String> bodyCache =
+	private final Map<MessageId, String> bodyCache =
 			new ConcurrentHashMap<>();
 
 	private volatile GroupId groupId;
@@ -52,10 +55,11 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	protected ThreadListListener<H> listener;
 
 	protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
-			LifecycleManager lifecycleManager,
+			LifecycleManager lifecycleManager, IdentityManager identityManager,
 			@CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
 			AndroidNotificationManager notificationManager) {
 		super(dbExecutor, lifecycleManager);
+		this.identityManager = identityManager;
 		this.cryptoExecutor = cryptoExecutor;
 		this.eventBus = eventBus;
 		this.notificationManager = notificationManager;
@@ -76,15 +80,14 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	@CallSuper
 	@Override
 	public void onActivityResume() {
-		checkGroupId();
-		notificationManager.blockNotification(groupId);
+		notificationManager.blockNotification(getGroupId());
 		eventBus.addListener(this);
 	}
 
 	@CallSuper
 	@Override
 	public void onActivityPause() {
-		notificationManager.unblockNotification(groupId);
+		notificationManager.unblockNotification(getGroupId());
 		eventBus.removeListener(this);
 	}
 
@@ -97,7 +100,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	public void eventOccurred(Event e) {
 		if (e instanceof GroupRemovedEvent) {
 			GroupRemovedEvent s = (GroupRemovedEvent) e;
-			if (s.getGroup().getId().equals(groupId)) {
+			if (s.getGroup().getId().equals(getGroupId())) {
 				LOG.info("Group removed");
 				listener.runOnUiThreadUnlessDestroyed(new Runnable() {
 					@Override
@@ -118,7 +121,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 			public void run() {
 				try {
 					long now = System.currentTimeMillis();
-					G groupItem = loadGroupItem();
+					G groupItem = loadNamedGroup();
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info(
@@ -134,7 +137,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	}
 
 	@DatabaseExecutor
-	protected abstract G loadGroupItem() throws DbException;
+	protected abstract G loadNamedGroup() throws DbException;
 
 	@Override
 	public void loadItems(
@@ -152,10 +155,14 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading headers took " + duration + " ms");
 
-					// Load bodies
+					// Load bodies into cache
 					now = System.currentTimeMillis();
-					Map<MessageId, String> bodies = loadBodies(headers);
-					bodyCache.putAll(bodies);
+					for (H header : headers) {
+						if (!bodyCache.containsKey(header.getId())) {
+							bodyCache.put(header.getId(),
+									loadMessageBody(header.getId()));
+						}
+					}
 					duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading bodies took " + duration + " ms");
@@ -175,8 +182,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	protected abstract Collection<H> loadHeaders() throws DbException;
 
 	@DatabaseExecutor
-	protected abstract Map<MessageId, String> loadBodies(Collection<H> headers)
-			throws DbException;
+	protected abstract String loadMessageBody(MessageId id) throws DbException;
 
 	@Override
 	public void loadItem(final H header,
@@ -186,9 +192,13 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 			public void run() {
 				LOG.info("Loading item...");
 				try {
-					String body = loadBodies(Collections.singletonList(header))
-							.get(header.getId());
-					bodyCache.put(header.getId(), body);
+					String body;
+					if (!bodyCache.containsKey(header.getId())) {
+						body = loadMessageBody(header.getId());
+						bodyCache.put(header.getId(), body);
+					} else {
+						body = bodyCache.get(header.getId());
+					}
 					I item = buildItem(header, body);
 					handler.onResult(item);
 				} catch (DbException e) {
@@ -230,21 +240,19 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	protected abstract void markRead(MessageId id) throws DbException;
 
 	@Override
-	public void send(String body,
-			ResultExceptionHandler<I, DbException> resultHandler) {
-		send(body, null, resultHandler);
-	}
-
-	@Override
-	public void send(final String body, @Nullable final MessageId parentId,
+	public void createAndStoreMessage(final String body,
+			@Nullable final MessageId parentId,
 			final ResultExceptionHandler<I, DbException> handler) {
-		cryptoExecutor.execute(new Runnable() {
+		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
-				LOG.info("Creating message...");
 				try {
-					M msg = createLocalMessage(body, parentId);
-					storePost(msg, body, handler);
+					LocalAuthor author = identityManager.getLocalAuthor();
+					long timestamp = getLatestTimestamp();
+					timestamp =
+							Math.max(timestamp, System.currentTimeMillis());
+					createMessage(body, timestamp, parentId, author,
+							handler);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -255,8 +263,24 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	}
 
 	@DatabaseExecutor
-	protected abstract M createLocalMessage(String body,
-			@Nullable MessageId parentId) throws DbException;
+	protected abstract long getLatestTimestamp() throws DbException;
+
+	private void createMessage(final String body, final long timestamp,
+			final @Nullable MessageId parentId, final LocalAuthor author,
+			final ResultExceptionHandler<I, DbException> handler) {
+		cryptoExecutor.execute(new Runnable() {
+			@Override
+			public void run() {
+				LOG.info("Creating message...");
+				M msg = createLocalMessage(body, timestamp, parentId, author);
+				storePost(msg, body, handler);
+			}
+		});
+	}
+
+	@CryptoExecutor
+	protected abstract M createLocalMessage(String body, long timestamp,
+			@Nullable MessageId parentId, LocalAuthor author);
 
 	private void storePost(final M msg, final String body,
 			final ResultExceptionHandler<I, DbException> resultHandler) {
@@ -292,8 +316,8 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 			public void run() {
 				try {
 					long now = System.currentTimeMillis();
-					G groupItem = loadGroupItem();
-					deleteGroupItem(groupItem);
+					G groupItem = loadNamedGroup();
+					deleteNamedGroup(groupItem);
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Removing group took " + duration + " ms");
@@ -309,7 +333,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
 	}
 
 	@DatabaseExecutor
-	protected abstract void deleteGroupItem(G groupItem) throws DbException;
+	protected abstract void deleteNamedGroup(G groupItem) throws DbException;
 
 	private List<I> buildItems(Collection<H> headers) {
 		List<I> entries = new ArrayList<>();
diff --git a/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java b/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java
index 4a7fcf02e0b766b20d601c8f4f1478ea5361f238..e6290e9716af40673203e0cfba28027469a8b249 100644
--- a/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java
+++ b/briar-android/test/java/org/briarproject/android/forum/ForumActivityTest.java
@@ -11,14 +11,12 @@ import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
-import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricGradleTestRunner;
@@ -82,7 +80,7 @@ public class ForumActivityTest {
 
 	private TestForumActivity forumActivity;
 	@Captor
-	private ArgumentCaptor<UiResultExceptionHandler<Collection<ForumEntry>, DbException>>
+	private ArgumentCaptor<UiResultExceptionHandler<Collection<ForumItem>, DbException>>
 			rc;
 
 	@Before
@@ -94,14 +92,14 @@ public class ForumActivityTest {
 				.withIntent(intent).create().resume().get();
 	}
 
-	private List<ForumEntry> getDummyData() {
-		ForumEntry[] forumEntries = new ForumEntry[6];
+	private List<ForumItem> getDummyData() {
+		ForumItem[] forumEntries = new ForumItem[6];
 		for (int i = 0; i < forumEntries.length; i++) {
 			AuthorId authorId = new AuthorId(TestUtils.getRandomId());
 			byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
 			Author author = new Author(authorId, AUTHORS[i], publicKey);
 			forumEntries[i] =
-					new ForumEntry(AUTHOR_IDS[i], PARENT_AUTHOR_IDS[i],
+					new ForumItem(AUTHOR_IDS[i], PARENT_AUTHOR_IDS[i],
 							AUTHORS[i], System.currentTimeMillis(), author,
 							UNKNOWN);
 			forumEntries[i].setLevel(LEVELS[i]);
@@ -112,7 +110,7 @@ public class ForumActivityTest {
 	@Test
 	public void testNestedEntries() {
 		ForumController mc = forumActivity.getController();
-		List<ForumEntry> dummyData = getDummyData();
+		List<ForumItem> dummyData = getDummyData();
 		verify(mc, times(1)).loadItems(rc.capture());
 		rc.getValue().onResult(dummyData);
 		NestedForumAdapter adapter = forumActivity.getAdapter();
diff --git a/briar-api/src/org/briarproject/api/blogs/Blog.java b/briar-api/src/org/briarproject/api/blogs/Blog.java
index 14bfbd97bdb883ef97db58ef34a88b3132e6b7db..153eb6e27ccef2ccf6ca0d73d6aae7935f9287df 100644
--- a/briar-api/src/org/briarproject/api/blogs/Blog.java
+++ b/briar-api/src/org/briarproject/api/blogs/Blog.java
@@ -7,9 +7,9 @@ import org.briarproject.api.sharing.Shareable;
 import org.briarproject.api.sync.Group;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
 
-@ThreadSafe
+@Immutable
 @NotNullByDefault
 public class Blog extends BaseGroup implements Shareable {
 
diff --git a/briar-api/src/org/briarproject/api/clients/BaseGroup.java b/briar-api/src/org/briarproject/api/clients/BaseGroup.java
index d2e1e8428a4a6442fd91f35d323f009f29021968..f101106e38c1d836e3f1c0d1366e867bdcebb924 100644
--- a/briar-api/src/org/briarproject/api/clients/BaseGroup.java
+++ b/briar-api/src/org/briarproject/api/clients/BaseGroup.java
@@ -5,9 +5,9 @@ import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
 
-@ThreadSafe
+@Immutable
 @NotNullByDefault
 public abstract class BaseGroup {
 
diff --git a/briar-api/src/org/briarproject/api/clients/NamedGroup.java b/briar-api/src/org/briarproject/api/clients/NamedGroup.java
index c41429e7aca8f33963a68a0ba4db63c39d49225d..f4b95fdc5cd5a8ced6eb8f18fe9f1b2540d4ce12 100644
--- a/briar-api/src/org/briarproject/api/clients/NamedGroup.java
+++ b/briar-api/src/org/briarproject/api/clients/NamedGroup.java
@@ -4,9 +4,9 @@ import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.Group;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
 
-@ThreadSafe
+@Immutable
 @NotNullByDefault
 public abstract class NamedGroup extends BaseGroup {
 
diff --git a/briar-api/src/org/briarproject/api/forum/Forum.java b/briar-api/src/org/briarproject/api/forum/Forum.java
index 3cbf297a07792509062813248ff34cb4bd9a8873..f73cd049afff86ea54c15bb905b9dd9bb4ccdd0f 100644
--- a/briar-api/src/org/briarproject/api/forum/Forum.java
+++ b/briar-api/src/org/briarproject/api/forum/Forum.java
@@ -5,9 +5,9 @@ import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sharing.Shareable;
 import org.briarproject.api.sync.Group;
 
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
 
-@ThreadSafe
+@Immutable
 @NotNullByDefault
 public class Forum extends NamedGroup implements Shareable {
 
diff --git a/briar-api/src/org/briarproject/api/forum/ForumManager.java b/briar-api/src/org/briarproject/api/forum/ForumManager.java
index edfd53f5508580907090f0944bc380fea4515651..7561fc9636ab1a0544ee8d4212c00a47d3067c50 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumManager.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumManager.java
@@ -1,8 +1,10 @@
 package org.briarproject.api.forum;
 
 import org.briarproject.api.clients.MessageTracker;
+import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
@@ -22,8 +24,9 @@ public interface ForumManager extends MessageTracker {
 	void removeForum(Forum f) throws DbException;
 
 	/** Creates a local forum post. */
-	ForumPost createLocalPost(GroupId groupId, String text,
-			@Nullable MessageId parentId) throws DbException;
+	@CryptoExecutor
+	ForumPost createLocalPost(GroupId groupId, String body, long timestamp,
+			@Nullable MessageId parentId, LocalAuthor author);
 
 	/** Stores a local forum post. */
 	ForumPostHeader addLocalPost(ForumPost p) throws DbException;
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
index 3141ab40b7af64a8a8bc73622766576ff4a06e95..310611361b960acc44ef6b5e9f6d30207f488360 100644
--- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
@@ -6,9 +6,9 @@ import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.Group;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
 
-@ThreadSafe
+@Immutable
 @NotNullByDefault
 public class PrivateGroup extends NamedGroup {
 
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
index 07ef1c002217399f893d71e03a6e15d6c776c65e..956a42344649b2d88cdbf0924d4c5ab1cda0986d 100644
--- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
@@ -3,6 +3,7 @@ package org.briarproject.api.privategroup;
 import org.briarproject.api.clients.MessageTracker;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
@@ -21,8 +22,8 @@ public interface PrivateGroupManager extends MessageTracker {
 	void removePrivateGroup(GroupId g) throws DbException;
 
 	/** Creates a local group message. */
-	GroupMessage createLocalMessage(GroupId groupId, String text,
-			@Nullable MessageId parentId) throws DbException;
+	GroupMessage createLocalMessage(GroupId groupId, String body,
+			long timestamp, @Nullable MessageId parentId, LocalAuthor author);
 
 	/** Stores (and sends) a local group message. */
 	GroupMessageHeader addLocalMessage(GroupMessage p) throws DbException;
diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index 4983385c0a956514da6ebd08b7a13de43affd75e..8d1511f7d8a8e2665fe4c5781d9c1e95b91a4542 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -25,7 +25,6 @@ import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
-import org.briarproject.api.system.Clock;
 import org.briarproject.clients.BdfIncomingMessageHook;
 import org.briarproject.util.StringUtils;
 import org.jetbrains.annotations.Nullable;
@@ -64,20 +63,17 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 	private final IdentityManager identityManager;
 	private final ForumFactory forumFactory;
 	private final ForumPostFactory forumPostFactory;
-	private final Clock clock;
 	private final List<RemoveForumHook> removeHooks;
 
 	@Inject
 	ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager,
 			ClientHelper clientHelper, MetadataParser metadataParser,
-			ForumFactory forumFactory, ForumPostFactory forumPostFactory,
-			Clock clock) {
+			ForumFactory forumFactory, ForumPostFactory forumPostFactory) {
 		super(db, clientHelper, metadataParser);
 
 		this.identityManager = identityManager;
 		this.forumFactory = forumFactory;
 		this.forumPostFactory = forumPostFactory;
-		this.clock = clock;
 		removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
 	}
 
@@ -129,23 +125,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 	}
 
 	@Override
-	public ForumPost createLocalPost(final GroupId groupId,
-			final String body, final @Nullable MessageId parentId)
-			throws DbException {
-
-		LocalAuthor author;
-		GroupCount count;
-		Transaction txn = db.startTransaction(true);
-		try {
-			author = identityManager.getLocalAuthor(txn);
-			count = getGroupCount(txn, groupId);
-			txn.setComplete();
-		} finally {
-			db.endTransaction(txn);
-		}
-		long timestamp = clock.currentTimeMillis();
-		timestamp = Math.max(timestamp, count.getLatestMsgTime());
-
+	public ForumPost createLocalPost(final GroupId groupId, final String body,
+			final long timestamp, final @Nullable MessageId parentId,
+			final LocalAuthor author) {
 		ForumPost p;
 		try {
 			p = forumPostFactory
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
index b0ef7e072db3382199f5d99fad503c12f0a7df4f..6f40bd9c6c9c069c4e9de80d9cbfa7c887626b53 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -77,16 +77,13 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 
 	@Override
 	public GroupMessage createLocalMessage(GroupId groupId, String body,
-			@Nullable MessageId parentId) throws DbException {
-
-		long timestamp = clock.currentTimeMillis();
-		LocalAuthor author = identityManager.getLocalAuthor();
+			long timestamp, @Nullable MessageId parentId, LocalAuthor author) {
 		try {
 			return groupMessageFactory
 					.createGroupMessage(groupId, timestamp, parentId, author,
 							body);
 		} catch (FormatException e) {
-			throw new DbException(e);
+			throw new RuntimeException(e);
 		} catch (GeneralSecurityException e) {
 			throw new RuntimeException(e);
 		}
diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java
index f8a97a6d635bd403c59eb9ea659decdde3c68f5a..ec43eebf75e42e3d8bb480eee52f55b2e3b098dd 100644
--- a/briar-core/src/org/briarproject/util/StringUtils.java
+++ b/briar-core/src/org/briarproject/util/StringUtils.java
@@ -86,4 +86,11 @@ public class StringUtils {
 	public static String trim(String s) {
 		return s.trim();
 	}
+
+	/**
+	 * Returns true if the string is longer than maxLength
+	 */
+	public static boolean isTooLong(String s, int maxLength) {
+		return toUtf8(s).length > maxLength;
+	}
 }