diff --git a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
index 70d326ca42266b50da015e30191740ad786e0a2c..e91088f0271f920e236e701c7fb0afa23e4935c8 100644
--- a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
@@ -128,12 +128,7 @@ public class WriteBlogPostActivity extends BriarActivity
 	}
 
 	private void enableOrDisablePublishButton() {
-		int bodyLength =
-				StringUtils.toUtf8(input.getText().toString()).length;
-		if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH)
-			input.setSendButtonEnabled(true);
-		else
-			input.setSendButtonEnabled(false);
+		input.setSendButtonEnabled(input.getText().length() > 0);
 	}
 
 	@Override
@@ -142,6 +137,7 @@ public class WriteBlogPostActivity extends BriarActivity
 		input.setVisibility(GONE);
 		progressBar.setVisibility(VISIBLE);
 
+		body = StringUtils.truncateUtf8(body, MAX_BLOG_POST_BODY_LENGTH);
 		storePost(body);
 	}
 
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index 1d563ad0ce1679a7d218e911d083d3d270eb95d4..af2e5e0b44ef156dc0f15d7100e0eae8cf08327d 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -100,6 +100,7 @@ import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.contact.ConversationItem.IncomingItem;
 import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
 import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE;
+import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
 
 public class ConversationActivity extends BriarActivity
 		implements EventListener, IntroductionHandler, TextInputListener {
@@ -656,6 +657,7 @@ public class ConversationActivity extends BriarActivity
 	public void onSendClick(String text) {
 		markMessagesRead();
 		if (text.equals("")) return;
+		text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
 		long timestamp = System.currentTimeMillis();
 		timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
 		createMessage(StringUtils.toUtf8(text), timestamp);
diff --git a/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java b/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java
index 4b5b3c3cd0cbfed5282613b3e8c69005d5a1d6a4..019aff689978c3354668d9af1e38cfebc26d8768 100644
--- a/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java
+++ b/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java
@@ -20,6 +20,7 @@ import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.introduction.IntroductionManager;
+import org.briarproject.util.StringUtils;
 
 import java.util.logging.Logger;
 
@@ -33,6 +34,7 @@ import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static android.widget.Toast.LENGTH_SHORT;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
 
 public class IntroductionMessageFragment extends BaseFragment
 		implements TextInputView.TextInputListener {
@@ -176,6 +178,7 @@ public class IntroductionMessageFragment extends BaseFragment
 		ui.message.setSendButtonEnabled(false);
 
 		String msg = ui.message.getText().toString();
+		msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH);
 		makeIntroduction(contact1, contact2, msg);
 
 		// don't wait for the introduction to be made before finishing activity
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java b/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java
index 5a8f4747639dec6154ac2de9f03c82bc01c44f29..ea79398cf0167b62722e3fdbad7417760fa6cc38 100644
--- a/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java
+++ b/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java
@@ -15,6 +15,7 @@ import org.briarproject.api.blogs.BlogSharingManager;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.forum.ForumSharingManager;
 import org.briarproject.api.sync.GroupId;
+import org.briarproject.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -24,6 +25,7 @@ import javax.inject.Inject;
 import static org.briarproject.android.sharing.ShareActivity.CONTACTS;
 import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds;
 import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
+import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
 
 abstract class ShareMessageFragment extends BaseFragment
 		implements TextInputListener {
@@ -103,6 +105,7 @@ abstract class ShareMessageFragment extends BaseFragment
 		// disable button to prevent accidental double invitations
 		ui.message.setSendButtonEnabled(false);
 
+		msg = StringUtils.truncateUtf8(msg, MAX_INVITATION_MESSAGE_LENGTH);
 		share(msg);
 
 		// don't wait for the invitation to be made before finishing activity
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
index 13e450c1c77843198469dfc8746afbbf09dd64f4..e88fb182c7f40dc4e5cf5e0f0165a589a17de7ef 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
@@ -13,6 +13,9 @@ public interface BlogConstants {
 	/** The maximum length of a blog post's body in bytes. */
 	int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
+	/** The maximum length of a blog comment in bytes. */
+	int MAX_BLOG_COMMENT_LENGTH = MAX_BLOG_POST_BODY_LENGTH;
+
 	/** The internal name of personal blogs that are created automatically */
 	String PERSONAL_BLOG_NAME = "briar.PERSONAL_BLOG_NAME";
 
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java
index 5a75b76475527f93a22fa98bf31e2102e4c88d03..8584dff59a58243709a24eeb14cba59bbcf690b0 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java
@@ -1,61 +1,7 @@
 package org.briarproject.api.blogs;
 
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.sharing.InvitationItem;
-import org.briarproject.api.sharing.InvitationMessage;
 import org.briarproject.api.sharing.SharingManager;
-import org.briarproject.api.sync.ClientId;
-import org.briarproject.api.sync.GroupId;
-
-import java.util.Collection;
 
 public interface BlogSharingManager extends SharingManager<Blog> {
 
-	/**
-	 * Returns the unique ID of the blog sharing client.
-	 */
-	ClientId getClientId();
-
-	/**
-	 * Sends an invitation to share the given blog with the given contact
-	 * and sends an optional message along with it.
-	 */
-	void sendInvitation(GroupId groupId, ContactId contactId,
-			String message) throws DbException;
-
-	/**
-	 * Responds to a pending blog invitation
-	 */
-	void respondToInvitation(Blog b, Contact c, boolean accept)
-			throws DbException;
-
-	/**
-	 * Returns all blogs sharing messages sent by the Contact
-	 * identified by contactId.
-	 */
-	Collection<InvitationMessage> getInvitationMessages(
-			ContactId contactId) throws DbException;
-
-	/**
-	 * Returns all blogs to which the user has been invited.
-	 */
-	Collection<InvitationItem> getInvitations() throws DbException;
-
-	/**
-	 * Returns all contacts who are sharing the given blog with us.
-	 */
-	Collection<Contact> getSharedBy(GroupId g) throws DbException;
-
-	/**
-	 * Returns the IDs of all contacts with whom the given blog is shared.
-	 */
-	Collection<Contact> getSharedWith(GroupId g) throws DbException;
-
-	/**
-	 * Returns true if the blog not already shared and no invitation is open
-	 */
-	boolean canBeShared(GroupId g, Contact c) throws DbException;
-
 }
diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java
index a734abc7d794863f0f0e668d3881d92292dba07a..ec0135c75f71e14659082fe955e828cdbd8f2ae5 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java
@@ -1,51 +1,7 @@
 package org.briarproject.api.forum;
 
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.sharing.InvitationItem;
-import org.briarproject.api.sharing.InvitationMessage;
 import org.briarproject.api.sharing.SharingManager;
-import org.briarproject.api.sync.ClientId;
-import org.briarproject.api.sync.GroupId;
-
-import java.util.Collection;
 
 public interface ForumSharingManager extends SharingManager<Forum> {
 
-	/** Returns the unique ID of the forum sharing client. */
-	ClientId getClientId();
-
-	/**
-	 * Sends an invitation to share the given forum with the given contact
-	 * and sends an optional message along with it.
-	 */
-	void sendInvitation(GroupId groupId, ContactId contactId,
-			String message)	throws DbException;
-
-	/**
-	 * Responds to a pending forum invitation
-	 */
-	void respondToInvitation(Forum f, Contact c, boolean accept)
-			throws DbException;
-
-	/**
-	 * Returns all forum sharing messages sent by the Contact
-	 * identified by contactId.
-	 */
-	Collection<InvitationMessage> getInvitationMessages(
-			ContactId contactId) throws DbException;
-
-	/** Returns all forums to which the user has been invited. */
-	Collection<InvitationItem> getInvitations() throws DbException;
-
-	/** Returns all contacts who are sharing the given forum with us. */
-	Collection<Contact> getSharedBy(GroupId g) throws DbException;
-
-	/** Returns the IDs of all contacts with whom the given forum is shared. */
-	Collection<Contact> getSharedWith(GroupId g) throws DbException;
-
-	/** Returns true if the forum not already shared and no invitation is open */
-	boolean canBeShared(GroupId g, Contact c) throws DbException;
-
 }
diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
index de74b390ff1934878fd82ccc54e2accf886e55c6..4e0e0582836364bd721f3914746fabce09f4f836 100644
--- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
+++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
@@ -1,5 +1,7 @@
 package org.briarproject.api.introduction;
 
+import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+
 public interface IntroductionConstants {
 
 	/* Protocol roles */
@@ -30,8 +32,18 @@ public interface IntroductionConstants {
 	String SIGNATURE = "signature";
 
 	/* Validation Constants */
+
+	/**
+	 * The length of the message authentication code in bytes.
+	 */
 	int MAC_LENGTH = 32;
 
+	/**
+	 * The maximum length of the introducer's optional message to the
+	 * introducees in UTF-8 bytes.
+	 */
+	int MAX_INTRODUCTION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
+
 	/* Introducer Local State Metadata */
 	String STATE = "state";
 	String ROLE = "role";
diff --git a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java
index 47a990669d9ebd0098341e2b89207e6455bd3035..8826b524bb00a9c13e648dd1e95855b2a21ea8f4 100644
--- a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java
+++ b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java
@@ -1,10 +1,20 @@
 package org.briarproject.api.sharing;
 
+import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+
 public interface SharingConstants {
 
-	/** The length of a sharing session's random salt in bytes. */
+	/**
+	 * The length of a sharing session's random salt in bytes.
+	 */
 	int SHARING_SALT_LENGTH = 32;
 
+	/**
+	 * The maximum length of the optional message from the inviter to the
+	 * invitee in UTF-8 bytes.
+	 */
+	int MAX_INVITATION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1204;
+
 	String CONTACT_ID = "contactId";
 	String GROUP_ID = "groupId";
 	String TO_BE_SHARED_BY_US = "toBeSharedByUs";
diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java
index c633256f1245fe0e4b98f3aa020ae8680690a1a6..b4613cd94035ef209d7d82b24c83a6126a841453 100644
--- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java
+++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java
@@ -11,15 +11,17 @@ import java.util.Collection;
 
 public interface SharingManager<S extends Shareable> extends MessageTracker {
 
-	/** Returns the unique ID of the group sharing client. */
+	/**
+	 * Returns the unique ID of the group sharing client.
+	 */
 	ClientId getClientId();
 
 	/**
-	 * Sends an invitation to share the given shareable with the given contact
+	 * Sends an invitation to share the given group with the given contact
 	 * and sends an optional message along with it.
 	 */
 	void sendInvitation(GroupId groupId, ContactId contactId,
-			String message)	throws DbException;
+			String message) throws DbException;
 
 	/**
 	 * Responds to a pending group invitation
@@ -28,22 +30,29 @@ public interface SharingManager<S extends Shareable> extends MessageTracker {
 			throws DbException;
 
 	/**
-	 * Returns all group sharing messages sent by the Contact
-	 * identified by contactId.
+	 * Returns all group sharing messages sent by the given contact.
 	 */
 	Collection<InvitationMessage> getInvitationMessages(
 			ContactId contactId) throws DbException;
 
-	/** Returns all invitations to shareables. */
+	/**
+	 * Returns all invitations to groups.
+	 */
 	Collection<InvitationItem> getInvitations() throws DbException;
 
-	/** Returns all contacts who are sharing the given group with us. */
+	/**
+	 * Returns all contacts who are sharing the given group with us.
+	 */
 	Collection<Contact> getSharedBy(GroupId g) throws DbException;
 
-	/** Returns the IDs of all contacts with whom the given group is shared. */
+	/**
+	 * Returns all contacts with whom the given group is shared.
+	 */
 	Collection<Contact> getSharedWith(GroupId g) throws DbException;
 
-	/** Returns true if the group not already shared and no invitation is open */
+	/**
+	 * Returns true if the group not already shared and no invitation is open
+	 */
 	boolean canBeShared(GroupId g, Contact c) throws DbException;
 
 }
diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
index 62e6bc0addaecdd2e868bd780d68205f54f08e36..fdab87cac682357641e27d1b1f92674b197b85c3 100644
--- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
+++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
@@ -11,6 +11,7 @@ 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.util.StringUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -18,6 +19,7 @@ import java.security.GeneralSecurityException;
 
 import javax.inject.Inject;
 
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_COMMENT_LENGTH;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
 import static org.briarproject.api.blogs.MessageType.COMMENT;
 import static org.briarproject.api.blogs.MessageType.POST;
@@ -42,7 +44,8 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 			throws FormatException, GeneralSecurityException {
 
 		// Validate the arguments
-		if (body.length() > MAX_BLOG_POST_BODY_LENGTH)
+		int bodyLength = StringUtils.toUtf8(body).length;
+		if (bodyLength > MAX_BLOG_POST_BODY_LENGTH)
 			throw new IllegalArgumentException();
 
 		// Serialise the data to be signed
@@ -62,6 +65,13 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 			@Nullable String comment, MessageId pOriginalId, MessageId parentId)
 			throws FormatException, GeneralSecurityException {
 
+		if (comment != null) {
+			int commentLength = StringUtils.toUtf8(comment).length;
+			if (commentLength == 0) throw new IllegalArgumentException();
+			if (commentLength > MAX_BLOG_COMMENT_LENGTH)
+				throw new IllegalArgumentException();
+		}
+
 		long timestamp = clock.currentTimeMillis();
 
 		// Generate the signature
diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
index 328153ad7f764443f6843fcda35097e8e979080e..29908c6d3d8230368e9fe4abc508e2eeefe12f8e 100644
--- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
+++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
@@ -40,6 +40,7 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_COMMENT_LENGTH;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
 import static org.briarproject.api.blogs.MessageType.COMMENT;
 import static org.briarproject.api.blogs.MessageType.POST;
@@ -125,7 +126,7 @@ class BlogPostValidator extends BdfMessageValidator {
 
 		// Comment
 		String comment = body.getOptionalString(0);
-		checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH);
+		checkLength(comment, 1, MAX_BLOG_COMMENT_LENGTH);
 
 		// parent_original_id
 		// The ID of a post or comment in this group or another group
@@ -216,7 +217,7 @@ class BlogPostValidator extends BdfMessageValidator {
 
 		// Body of Wrapped Comment
 		String comment = body.getOptionalString(2);
-		checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH);
+		checkLength(comment, 1, MAX_BLOG_COMMENT_LENGTH);
 
 		// c_parent_original_id
 		// Taken from the original comment
diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
index fda189d3a77ee975ae289bb655e745b9eb744802..e3daef72e5abe558994e59a91c8a944131617031 100644
--- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
+++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
@@ -484,10 +484,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 
 	private String getPostBody(String text) {
 		text = clean(text, article);
-		byte[] textBytes = StringUtils.toUtf8(text);
-		if (textBytes.length <= MAX_BLOG_POST_BODY_LENGTH)
-			return text;
-		return StringUtils.fromUtf8(textBytes, 0, MAX_BLOG_POST_BODY_LENGTH);
+		return StringUtils.truncateUtf8(text, MAX_BLOG_POST_BODY_LENGTH);
 	}
 
 	/**
diff --git a/briar-core/src/org/briarproject/introduction/IntroducerManager.java b/briar-core/src/org/briarproject/introduction/IntroducerManager.java
index b8c414a4778e1c80940e7bba523a8259aa8edc47..a7ecd00cbe07ca81b03a281a7622c86dacac9bcd 100644
--- a/briar-core/src/org/briarproject/introduction/IntroducerManager.java
+++ b/briar-core/src/org/briarproject/introduction/IntroducerManager.java
@@ -31,6 +31,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID
 import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_2;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2;
+import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY1;
@@ -103,7 +104,7 @@ class IntroducerManager {
 		return d;
 	}
 
-	public void makeIntroduction(Transaction txn, Contact c1, Contact c2,
+	void makeIntroduction(Transaction txn, Contact c1, Contact c2,
 			String msg, long timestamp) throws DbException, FormatException {
 
 		// TODO check for existing session with those contacts?
@@ -116,6 +117,9 @@ class IntroducerManager {
 		BdfDictionary localAction = new BdfDictionary();
 		localAction.put(TYPE, TYPE_REQUEST);
 		if (!StringUtils.isNullOrEmpty(msg)) {
+			int msgLength = StringUtils.toUtf8(msg).length;
+			if (msgLength > MAX_INTRODUCTION_MESSAGE_LENGTH)
+				throw new IllegalArgumentException();
 			localAction.put(MSG, msg);
 		}
 		localAction.put(PUBLIC_KEY1, c1.getAuthor().getPublicKey());
diff --git a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
index 3473ad61364779980c9868840ab3f9ad00f2c997..e2227e12eab424ab4fa2cead2ae29f70333e3240 100644
--- a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
+++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
@@ -21,6 +21,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH;
+import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
@@ -37,7 +38,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
 import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
 import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 class IntroductionValidator extends BdfMessageValidator {
 
@@ -92,7 +92,7 @@ class IntroductionValidator extends BdfMessageValidator {
 		String msg = null;
 		if (message.size() == 5) {
 			msg = message.getString(4);
-			checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH);
+			checkLength(msg, 0, MAX_INTRODUCTION_MESSAGE_LENGTH);
 		}
 
 		// Return the metadata
diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
index 7786dedee9cf585ec67e42c40c375b3d2b01d4b4..9248393b37c0118d9dc5ea293e075f9c248c3526 100644
--- a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
@@ -204,6 +204,7 @@ class BlogSharingManagerImpl extends
 					.createBlog(msg.getBlogTitle(), msg.getBlogDesc(), author);
 		}
 
+		@Override
 		public Blog parse(BlogInviteeSessionState state) {
 			Author author = authorFactory
 					.createAuthor(state.getBlogAuthorName(),
diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java b/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java
index 1a9f0ada3897cc22be668afd7c47c44cf7ea6f34..5f0988254537f03769bc801c3f362a3d2c30a8e4 100644
--- a/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java
+++ b/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java
@@ -16,14 +16,15 @@ import javax.inject.Inject;
 
 import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME;
 import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC;
-import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
 import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
 import static org.briarproject.api.sharing.SharingConstants.LOCAL;
+import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
 import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
@@ -32,7 +33,6 @@ import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVIT
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
 import static org.briarproject.api.sharing.SharingConstants.TIME;
 import static org.briarproject.api.sharing.SharingConstants.TYPE;
-import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 class BlogSharingValidator extends BdfMessageValidator {
 
@@ -76,7 +76,7 @@ class BlogSharingValidator extends BdfMessageValidator {
 
 			if (body.size() > 5) {
 				String msg = body.getString(5);
-				checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH);
+				checkLength(msg, 0, MAX_INVITATION_MESSAGE_LENGTH);
 				d.put(INVITATION_MSG, msg);
 			}
 		} else {
diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
index c78cda5bcc22b4bde0bf255f89cc7128791e08b3..092cf96f58edee95ab989df89d01e0b34c8fe18c 100644
--- a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
@@ -167,6 +167,7 @@ class ForumSharingManagerImpl extends
 					.createForum(msg.getForumName(), msg.getForumSalt());
 		}
 
+		@Override
 		public Forum parse(ForumInviteeSessionState state) {
 			return forumFactory
 					.createForum(state.getForumName(), state.getForumSalt());
diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java b/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java
index 702466bea05df11eb5887b354b41891ea25f4d80..11343f53803433a40675bb5ee916c27870ee8e17 100644
--- a/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java
+++ b/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java
@@ -1,9 +1,9 @@
 package org.briarproject.sharing;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.BdfMessageContext;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.SessionId;
-import org.briarproject.api.clients.BdfMessageContext;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
@@ -20,6 +20,7 @@ import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
 import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
 import static org.briarproject.api.sharing.SharingConstants.LOCAL;
+import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
 import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
@@ -28,7 +29,6 @@ import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVIT
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
 import static org.briarproject.api.sharing.SharingConstants.TIME;
 import static org.briarproject.api.sharing.SharingConstants.TYPE;
-import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 class ForumSharingValidator extends BdfMessageValidator {
 
@@ -61,7 +61,7 @@ class ForumSharingValidator extends BdfMessageValidator {
 
 			if (body.size() > 4) {
 				String msg = body.getString(4);
-				checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH);
+				checkLength(msg, 0, MAX_INVITATION_MESSAGE_LENGTH);
 				d.put(INVITATION_MSG, msg);
 			}
 		} else {
diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
index 9def8487dc2b82b803b6baff9b0092062c13dc61..a7c90d0a78f45edcf201803856d1cce078572517 100644
--- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
@@ -29,7 +29,6 @@ import org.briarproject.api.sharing.InvitationItem;
 import org.briarproject.api.sharing.InvitationMessage;
 import org.briarproject.api.sharing.Shareable;
 import org.briarproject.api.sharing.SharingManager;
-import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
@@ -58,6 +57,7 @@ import static org.briarproject.api.clients.ProtocolEngine.StateUpdate;
 import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID;
 import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
 import static org.briarproject.api.sharing.SharingConstants.LOCAL;
+import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
 import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
 import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID;
 import static org.briarproject.api.sharing.SharingConstants.SHARED_BY_US;
@@ -115,8 +115,6 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
 		localGroup = contactGroupFactory.createLocalGroup(getClientId());
 	}
 
-	public abstract ClientId getClientId();
-
 	protected abstract InvitationMessage createInvitationRequest(MessageId id,
 			I msg, ContactId contactId, boolean available, long time,
 			boolean local, boolean sent, boolean seen, boolean read);
@@ -282,6 +280,9 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
 
 			// add invitation message to local state to be available for engine
 			if (!StringUtils.isNullOrEmpty(msg)) {
+				int msgLength = StringUtils.toUtf8(msg).length;
+				if (msgLength > MAX_INVITATION_MESSAGE_LENGTH)
+					throw new IllegalArgumentException();
 				localState.setMessage(msg);
 			}
 
diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java
index f8a97a6d635bd403c59eb9ea659decdde3c68f5a..2c67a81bb8c6002a56a9a2c9c07b6f41b7a3c01d 100644
--- a/briar-core/src/org/briarproject/util/StringUtils.java
+++ b/briar-core/src/org/briarproject/util/StringUtils.java
@@ -1,10 +1,18 @@
 package org.briarproject.util;
 
 import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
 import java.util.Collection;
 
+import static java.nio.charset.CodingErrorAction.IGNORE;
+
 public class StringUtils {
 
+	private static final Charset UTF_8 = Charset.forName("UTF-8");
+
 	private static final char[] HEX = new char[] {
 		'0', '1', '2', '3', '4', '5', '6', '7',
 		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
@@ -32,23 +40,29 @@ public class StringUtils {
 	}
 
 	public static String fromUtf8(byte[] bytes) {
-		try {
-			return new String(bytes, "UTF-8");
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
+		return fromUtf8(bytes, 0, bytes.length);
 	}
 
 	public static String fromUtf8(byte[] bytes, int off, int len) {
+		CharsetDecoder decoder = UTF_8.newDecoder();
+		decoder.onMalformedInput(IGNORE);
+		decoder.onUnmappableCharacter(IGNORE);
+		ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len);
 		try {
-			return new String(bytes, off, len, "UTF-8");
-		} catch (UnsupportedEncodingException e) {
+			return decoder.decode(buffer).toString();
+		} catch (CharacterCodingException e) {
 			throw new RuntimeException(e);
 		}
 	}
 
+	public static String truncateUtf8(String s, int maxUtf8Length) {
+		byte[] utf8 = toUtf8(s);
+		if (utf8.length <= maxUtf8Length) return s;
+		return fromUtf8(utf8, 0, maxUtf8Length);
+	}
+
 	/** Converts the given byte array to a hex character array. */
-	public static char[] toHexChars(byte[] bytes) {
+	private static char[] toHexChars(byte[] bytes) {
 		char[] hex = new char[bytes.length * 2];
 		for (int i = 0, j = 0; i < bytes.length; i++) {
 			hex[j++] = HEX[(bytes[i] >> 4) & 0xF];
diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java
index af9b2723cc9d8bc4b2e5399a7a56c9beef434147..19ea3b62b07613bd96fb7d7330488857a2d02171 100644
--- a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java
+++ b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java
@@ -30,6 +30,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH;
+import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
@@ -83,8 +84,10 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	public void testValidateProperIntroductionRequest() throws IOException {
 		final byte[] sessionId = TestUtils.getRandomId();
 		final String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
-		final byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		final String text = TestUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH);
+		final byte[] publicKey =
+				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+		final String text =
+				TestUtils.getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
 
 		BdfList body = BdfList.of(TYPE_REQUEST, sessionId,
 				name, publicKey, text);
@@ -107,14 +110,15 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 		// no NAME is message
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
-					msg.getRaw(PUBLIC_KEY));
+				msg.getRaw(PUBLIC_KEY));
 		if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
 
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testValidateIntroductionRequestWithLongName() throws IOException {
+	public void testValidateIntroductionRequestWithLongName()
+			throws IOException {
 		// too long NAME in message
 		BdfDictionary msg = getValidIntroductionRequest();
 		msg.put(NAME, msg.get(NAME) + "x");
@@ -315,7 +319,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	@Test(expected = FormatException.class)
-	public void testValidateIntroductionAckWithLongSessionId() throws IOException {
+	public void testValidateIntroductionAckWithLongSessionId()
+			throws IOException {
 		BdfDictionary msg = BdfDictionary.of(
 				new BdfEntry(TYPE, TYPE_ACK),
 				new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1])
diff --git a/briar-tests/src/org/briarproject/util/StringUtilsTest.java b/briar-tests/src/org/briarproject/util/StringUtilsTest.java
index 9ac929b3a3b31baca35ff352594ae8bb764b36f3..165b5bd8592b63d60dc3d9cb5a441a1e8e02696e 100644
--- a/briar-tests/src/org/briarproject/util/StringUtilsTest.java
+++ b/briar-tests/src/org/briarproject/util/StringUtilsTest.java
@@ -5,30 +5,172 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertSame;
 
 public class StringUtilsTest extends BriarTestCase {
 
 	@Test
 	public void testToHexString() {
-		byte[] b = new byte[] {1, 2, 3, 127, -128};
-		String s = StringUtils.toHexString(b);
-		assertEquals("0102037F80", s);
-	}
-
-	@Test
-	public void testFromHexString() {
-		try {
-			StringUtils.fromHexString("12345");
-			fail();
-		} catch (IllegalArgumentException expected) {}
-		try {
-			StringUtils.fromHexString("ABCDEFGH");
-			fail();
-		} catch (IllegalArgumentException expected) {}
-		byte[] b = StringUtils.fromHexString("0102037F80");
-		assertArrayEquals(new byte[] {1, 2, 3, 127, -128}, b);
-		b = StringUtils.fromHexString("0a0b0c0d0e0f");
-		assertArrayEquals(new byte[] {10, 11, 12, 13, 14, 15}, b);
+		byte[] b = new byte[] {
+				0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80,
+				0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF
+		};
+		String expected = "000102037F800A0B0C0D0EFF";
+		assertEquals(expected, StringUtils.toHexString(b));
+	}
+
+	@Test
+	public void testToHexStringEmptyInput() {
+		assertEquals("", StringUtils.toHexString(new byte[0]));
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testFromHexStringRejectsInvalidLength() {
+		StringUtils.fromHexString("12345");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testFromHexStringRejectsInvalidCharacter() {
+		StringUtils.fromHexString("ABCDEFGH");
+	}
+
+	@Test
+	public void testFromHexStringUppercase() {
+		String s = "000102037F800A0B0C0D0EFF";
+		byte[] expected = new byte[] {
+				0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80,
+				0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF
+		};
+		assertArrayEquals(expected, StringUtils.fromHexString(s));
+	}
+
+	@Test
+	public void testFromHexStringLowercase() {
+		String s = "000102037f800a0b0c0d0eff";
+		byte[] expected = new byte[] {
+				0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80,
+				0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF
+		};
+		assertArrayEquals(expected, StringUtils.fromHexString(s));
+	}
+
+	@Test
+	public void testFromHexStringEmptyInput() {
+		assertArrayEquals(new byte[0], StringUtils.fromHexString(""));
+	}
+
+	@Test
+	public void testToUtf8EncodesNullCharacterAsStandardUtf8() {
+		// The Unicode null character should be encoded as a single null byte,
+		// not as two bytes as in CESU-8 and modified UTF-8
+		String s = "\u0000";
+		assertArrayEquals(new byte[1], StringUtils.toUtf8(s));
+	}
+
+	@Test
+	public void testToUtf8EncodesSupplementaryCharactersAsStandardUtf8() {
+		// A supplementary character should be encoded as four bytes, not as a
+		// surrogate pair as in CESU-8 and modified UTF-8
+		String s = "\u0045\u0205\uD801\uDC00";
+		byte[] expected = new byte[] {
+				0x45, // U+0045
+				(byte) 0xC8, (byte) 0x85, // U+0205
+				(byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80 // U+10400
+		};
+		assertArrayEquals(expected, StringUtils.toUtf8(s));
+	}
+
+	@Test
+	public void testToUtf8EmptyInput() {
+		assertArrayEquals(new byte[0], StringUtils.toUtf8(""));
+	}
+
+	@Test
+	public void testFromUtf8AcceptsNullCharacterUsingStandardUtf8() {
+		// The UTF-8 encoding of the null character is valid
+		assertEquals("\u0000", StringUtils.fromUtf8(new byte[1]));
+	}
+
+	@Test
+	public void testFromUtf8RemovesNullCharacterUsingModifiedUtf8() {
+		// The modified UTF-8 encoding of the null character is not valid
+		byte[] b = new byte[] {
+				(byte) 0xC0, (byte) 0x80, // Null character as modified UTF-8
+				(byte) 0xC8, (byte) 0x85 // U+0205
+		};
+		// Conversion should ignore the invalid character and return the rest
+		String expected = "\u0205";
+		assertEquals(expected, StringUtils.fromUtf8(b));
+	}
+
+	@Test
+	public void testFromUtf8AcceptsSupplementaryCharacterUsingStandardUtf8() {
+		// The UTF-8 encoding of a supplementary character is valid and should
+		// be converted to a surrogate pair
+		byte[] b = new byte[] {
+				(byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80, // U+10400
+				(byte) 0xC8, (byte) 0x85 // U+0205
+		};
+		String expected = "\uD801\uDC00\u0205"; // Surrogate pair
+		assertEquals(expected, StringUtils.fromUtf8(b));
+	}
+
+	@Test
+	public void testFromUtf8RemovesSupplementaryCharacterUsingModifiedUtf8() {
+		// The CESU-8 or modified UTF-8 encoding of a supplementary character
+		// is not valid
+		byte[] b = new byte[] {
+				(byte) 0xED, (byte) 0xA0, (byte) 0x81, // U+10400 as CSEU-8
+				(byte) 0xED, (byte) 0xB0, (byte) 0x80,
+				(byte) 0xC8, (byte) 0x85 // U+0205
+		};
+		// Converstion should ignore the invalid character and return the rest
+		String expected = "\u0205";
+		assertEquals(expected, StringUtils.fromUtf8(b));
+	}
+
+	@Test
+	public void testFromUtf8EmptyInput() {
+		assertEquals("", StringUtils.fromUtf8(new byte[0]));
+	}
+
+	@Test
+	public void testTruncateUtf8ReturnsArgumentIfNotTruncated() {
+		String s = "Hello";
+		assertSame(s, StringUtils.truncateUtf8(s, 5));
+	}
+
+	@Test
+	public void testTruncateUtf8ChecksUtf8LengthNotStringLength() {
+		String s = "H\u0205llo";
+		assertEquals(5, s.length());
+		assertEquals(6, StringUtils.toUtf8(s).length);
+		String expected = "H\u0205ll"; // Sixth byte removed
+		assertEquals(expected, StringUtils.truncateUtf8(s, 5));
+	}
+
+	@Test
+	public void testTruncateUtf8RemovesTruncatedCharacter() {
+		String s = "\u0205\u0205"; // String requires four bytes
+		String expected = "\u0205"; // Partial character removed
+		String truncated = StringUtils.truncateUtf8(s, 3);
+		assertEquals(expected, truncated);
+		// Converting the truncated string should not exceed the max length
+		assertEquals(2, StringUtils.toUtf8(truncated).length);
+	}
+
+	@Test
+	public void testTruncateUtf8RemovesTruncatedSurrogatePair() {
+		String s = "\u0205\uD801\uDC00"; // String requires six bytes
+		String expected = "\u0205"; // Partial character removed
+		String truncated = StringUtils.truncateUtf8(s, 3);
+		assertEquals(expected, truncated);
+		// Converting the truncated string should not exceed the max length
+		assertEquals(2, StringUtils.toUtf8(truncated).length);
+	}
+
+	@Test
+	public void testTruncateUtf8EmptyInput() {
+		assertEquals("", StringUtils.truncateUtf8("", 123));
 	}
 }