Implementing IComparable<MessageId>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be9ea7d..a9141a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@
 
 ## [Unreleased]
 
+### Added
+
+- MessageId implements IComparable\<MessageId\>
+
 ### Fixed
 
 - Do not throw exceptions when disposing consumers, readers or producers
diff --git a/src/DotPulsar/MessageId.cs b/src/DotPulsar/MessageId.cs
index 27efc62..53bbaa9 100644
--- a/src/DotPulsar/MessageId.cs
+++ b/src/DotPulsar/MessageId.cs
@@ -20,7 +20,7 @@
     /// <summary>
     /// Unique identifier of a single message.
     /// </summary>
-    public sealed class MessageId : IEquatable<MessageId>
+    public sealed class MessageId : IEquatable<MessageId>, IComparable<MessageId>
     {
         static MessageId()
         {
@@ -72,6 +72,38 @@
         /// </summary>
         public int BatchIndex => Data.BatchIndex;
 
+        public int CompareTo(MessageId? other)
+        {
+            if (other is null)
+                return 1;
+
+            var result = LedgerId.CompareTo(other.LedgerId);
+            if (result != 0)
+                return result;
+
+            result = EntryId.CompareTo(other.EntryId);
+            if (result != 0)
+                return result;
+
+            result = Partition.CompareTo(other.Partition);
+            if (result != 0)
+                return result;
+
+            return BatchIndex.CompareTo(other.BatchIndex);
+        }
+
+        public static bool operator >(MessageId x, MessageId y)
+            => x is not null && x.CompareTo(y) >= 1;
+
+        public static bool operator <(MessageId x, MessageId y)
+            => x is not null ? x.CompareTo(y) <= -1 : y is not null;
+
+        public static bool operator >=(MessageId x, MessageId y)
+            => x is not null ? x.CompareTo(y) >= 0 : y is null;
+
+        public static bool operator <=(MessageId x, MessageId y)
+            => x is not null ? x.CompareTo(y) <= 0 : true;
+
         public override bool Equals(object? o)
             => o is MessageId id && Equals(id);
 
diff --git a/tests/DotPulsar.Tests/MessageIdTests.cs b/tests/DotPulsar.Tests/MessageIdTests.cs
index b3d1077..1b87744 100644
--- a/tests/DotPulsar.Tests/MessageIdTests.cs
+++ b/tests/DotPulsar.Tests/MessageIdTests.cs
@@ -12,7 +12,6 @@
  * limitations under the License.
  */
 
-// We cannot assume consumers of this library will obey the nullability guarantees
 #nullable disable
 
 namespace DotPulsar.Tests
@@ -24,9 +23,82 @@
     public class MessageIdTests
     {
         [Fact]
+        public void CompareTo_GivenTheSameValues_ShouldBeEqual()
+        {
+            var m1 = new MessageId(1, 2, 3, 4);
+            var m2 = new MessageId(1, 2, 3, 4);
+
+            m1.CompareTo(m2).Should().Be(0);
+            (m1 < m2).Should().BeFalse();
+            (m1 > m2).Should().BeFalse();
+            (m1 >= m2).Should().BeTrue();
+            (m1 <= m2).Should().BeTrue();
+        }
+
+        [Fact]
+        public void CompareTo_GivenAllNull_ShouldBeEqual()
+        {
+            MessageId m1 = null;
+            MessageId m2 = null;
+
+            (m1 < m2).Should().BeFalse();
+            (m1 > m2).Should().BeFalse();
+            (m1 <= m2).Should().BeTrue();
+            (m1 >= m2).Should().BeTrue();
+        }
+
+        [Fact]
+        public void CompareTo_GivenOneNull_ShouldFollowNull()
+        {
+            var m1 = new MessageId(1, 2, 3, 4);
+            MessageId m2 = null;
+
+            m1.CompareTo(m2).Should().BePositive();
+            (m1 < m2).Should().BeFalse();
+            (m1 > m2).Should().BeTrue();
+            (m1 <= m2).Should().BeFalse();
+            (m1 >= m2).Should().BeTrue();
+
+            (m2 < m1).Should().BeTrue();
+            (m2 > m1).Should().BeFalse();
+            (m2 <= m1).Should().BeTrue();
+            (m2 >= m1).Should().BeFalse();
+        }
+
+        [Theory]
+        [InlineData(2, 2, 3, 4)] // LegderId is greater
+        [InlineData(1, 3, 3, 4)] // EntryId is greater
+        [InlineData(1, 2, 4, 4)] // Partition is greater
+        [InlineData(1, 2, 3, 5)] // BatchIndex is greater
+        public void CompareTo_GivenCurrentFollowsArgument_ShouldReturnPositive(ulong ledgerId, ulong entryId, int partition, int batchIndex)
+        {
+            var m1 = new MessageId(ledgerId, entryId, partition, batchIndex);
+            var m2 = new MessageId(1, 2, 3, 4);
+
+            m1.CompareTo(m2).Should().BePositive();
+            (m1 > m2).Should().BeTrue();
+            (m1 < m2).Should().BeFalse();
+        }
+
+        [Theory]
+        [InlineData(0, 2, 3, 4)] // LegderId is less
+        [InlineData(1, 1, 3, 4)] // EntryId is less
+        [InlineData(1, 2, 2, 4)] // Partition is less
+        [InlineData(1, 2, 3, 3)] // BatchIndex is less
+        public void CompareTo_GivenCurrentPrecedesArgument_ShouldReturnNegative(ulong ledgerId, ulong entryId, int partition, int batchIndex)
+        {
+            var m1 = new MessageId(ledgerId, entryId, partition, batchIndex);
+            var m2 = new MessageId(1, 2, 3, 4);
+
+            m1.CompareTo(m2).Should().BeNegative();
+            (m1 < m2).Should().BeTrue();
+            (m1 > m2).Should().BeFalse();
+        }
+
+        [Fact]
         public void Equals_GivenTheSameObject_ShouldBeEqual()
         {
-            var m1 = new MessageId(1234, 5678, 9876, 5432);
+            var m1 = new MessageId(1, 2, 3, 4);
             var m2 = m1;
 
             m1.Equals(m2).Should().BeTrue();
@@ -37,19 +109,23 @@
         [Fact]
         public void Equals_GivenTheSameValues_ShouldBeEqual()
         {
-            var m1 = new MessageId(1234, 5678, 9876, 5432);
-            var m2 = new MessageId(1234, 5678, 9876, 5432);
+            var m1 = new MessageId(1, 2, 3, 4);
+            var m2 = new MessageId(1, 2, 3, 4);
 
             m1.Equals(m2).Should().BeTrue();
             (m1 == m2).Should().BeTrue();
             (m1 != m2).Should().BeFalse();
         }
 
-        [Fact]
-        public void Equals_GivenDifferentValues_ShouldNotBeEqual()
+        [Theory]
+        [InlineData(0, 2, 3, 4)] // LegerId not the same
+        [InlineData(1, 0, 3, 4)] // EntryId not the same
+        [InlineData(1, 2, 0, 4)] // Partition not the same
+        [InlineData(1, 2, 3, 0)] // BatchIndex not the same
+        public void Equals_GivenDifferentValues_ShouldNotBeEqual(ulong ledgerId, ulong entryId, int partition, int batchIndex)
         {
-            var m1 = new MessageId(1234, 5678, 9876, 5432);
-            var m2 = new MessageId(9876, 6432, 1234, 6678);
+            var m1 = new MessageId(ledgerId, entryId, partition, batchIndex);
+            var m2 = new MessageId(1, 2, 3, 4);
 
             m1.Equals(m2).Should().BeFalse();
             (m1 == m2).Should().BeFalse();
@@ -70,7 +146,7 @@
         [Fact]
         public void Equals_GivenOneNull_ShouldNotBeEqual()
         {
-            var m1 = new MessageId(1234, 5678, 9876, 5432);
+            var m1 = new MessageId(1, 2, 3, 4);
             MessageId m2 = null;
 
             (m1 == null).Should().BeFalse();
@@ -80,3 +156,5 @@
         }
     }
 }
+
+#nullable enable