JAMES-3381 email/query implements limit
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index 7792c9a..8381c45 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -96,10 +96,7 @@
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
-    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        AppendCommand.builder().withInternalDate(requestDate).build(message))
-      .getMessageId
+    val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDate)
 
     val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, AppendCommand.from(message))
@@ -223,20 +220,11 @@
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
-    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
-      .getMessageId
+    val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDateMessage1)
     val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(1).plusHours(1).toInstant)
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
-      .getMessageId
+    val messageId2 = sendMessageToBobInbox(server, message, requestDateMessage2)
     val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).plusHours(2).toInstant)
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
-      .getMessageId
+    val messageId3 = sendMessageToBobInbox(server, message, requestDateMessage3)
 
     val request =
       s"""{
@@ -782,10 +770,7 @@
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
-    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
-      .getMessageId
+    val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDateMessage1)
     val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, AppendCommand.from(message))
       .getMessageId
@@ -940,9 +925,7 @@
       .build
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val requestDate = ZonedDateTime.now().minusDays(1)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(Date.from(requestDate.toInstant)).build(message))
-      .getMessageId
+    val messageId1 = sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
 
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
@@ -994,9 +977,7 @@
       .build
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val requestDate = ZonedDateTime.now().minusDays(1)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(Date.from(requestDate.toInstant)).build(message))
-      .getMessageId
+    val messageId1 = sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
@@ -1048,13 +1029,11 @@
       .build
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
-    server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage1.toInstant)).build(message))
-      .getMessageId
+    sendMessageToBobInbox(server, message, Date.from(receivedDateMessage1.toInstant))
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
-    val receivedDateMessage2 = ZonedDateTime.now().minusDays(1).plusHours(2)
+    val receivedDateMessage2 = receivedDateMessage1.plusHours(2)
     val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage2.toInstant)).build(message))
       .getMessageId
@@ -1103,9 +1082,7 @@
       .build
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
-    server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage1.toInstant)).build(message))
-      .getMessageId
+    sendMessageToBobInbox(server, message, Date.from(receivedDateMessage1.toInstant))
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
@@ -1148,6 +1125,230 @@
     }
   }
 
+  @Test
+  def shouldLimitResultByTheLimitProvidedByTheClient(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
+    sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
+
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, otherMailboxPath, AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "limit": 1
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "ids": ["${messageId2.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def shouldReturnAnIllegalArgumentExceptionIfTheLimitIsNegative(server: GuiceJamesServer): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "limit": -1
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "invalidArguments",
+           |                "description": "The limit can not be negative. -1 was provided."
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def theLimitshouldBeEnforcedByTheServerIfNoLimitProvidedByTheClient(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val allMessages = (0 to 300).toList.foldLeft(List[MessageId](), ZonedDateTime.now().minusYears(1))((acc, _) => {
+      val (messageList, date) = acc
+      val dateForNewMessage = date.plusDays(1)
+      val messageId = sendMessageToBobInbox(server, message, Date.from(dateForNewMessage.toInstant))
+      (messageId :: messageList, dateForNewMessage)
+    })
+
+    val expectedMessages = allMessages._1.take(256)
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .inPath("$.methodResponses[0][1].ids")
+        .isEqualTo("[" + expectedMessages.map(message => s""""${message.serialize()}"""").mkString(", ") + "]")
+
+      assertThatJson(response)
+        .inPath("$.methodResponses[0][1].limit")
+        .isEqualTo("256")
+    }
+  }
+
+  @Test
+  def theLimitshouldBeEnforcedByTheServerIfAGreaterLimitProvidedByTheClient(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val allMessages = (0 to 300).toList.foldLeft(List[MessageId](), ZonedDateTime.now().minusYears(1))((acc, _) => {
+      val (messageList, date) = acc
+      val dateForNewMessage = date.plusDays(1)
+      val messageId = sendMessageToBobInbox(server, message, Date.from(dateForNewMessage.toInstant))
+      (messageId :: messageList, dateForNewMessage)
+    })
+
+    val expectedMessages = allMessages._1.take(256)
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "limit": 2000
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .inPath("$.methodResponses[0][1].ids")
+        .isEqualTo("[" + expectedMessages.map(message => s""""${message.serialize()}"""").mkString(", ") + "]")
+
+      assertThatJson(response)
+        .inPath("$.methodResponses[0][1].limit")
+        .isEqualTo("256")
+    }
+  }
+
+  private def sendMessageToBobInbox(server: GuiceJamesServer, message: Message, requestDate: Date) = {
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder().withInternalDate(requestDate).build(message))
+      .getMessageId
+  }
+
   @ParameterizedTest
   @MethodSource(value = Array("jmapSystemKeywords"))
   def listMailsBySystemKeywordShouldReturnOnlyMailsWithThisSystemKeyword(keywordFlag: Flags, keywordName: String, server: GuiceJamesServer): Unit = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
index 8251f96..a4609a6 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
@@ -20,8 +20,8 @@
 package org.apache.james.jmap.json
 
 import javax.inject.Inject
-import org.apache.james.jmap.mail.{CanCalculateChanges, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, IsAscending, Limit, Position, QueryState, ReceivedAtSortProperty, SortProperty}
-import org.apache.james.jmap.model.{AccountId, Keyword}
+import org.apache.james.jmap.mail.{Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, IsAscending, ReceivedAtSortProperty, SortProperty}
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, LimitUnparsed, Position, QueryState}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
 
@@ -49,11 +49,11 @@
     case _ => JsError("Expecting keywords to be represented by a JsString")
   }
   private implicit val filterConditionReads: Reads[FilterCondition] = Json.reads[FilterCondition]
+  private implicit val limitUnparsedReads: Reads[LimitUnparsed] = Json.valueReads[LimitUnparsed]
   private implicit val CanCalculateChangesFormat: Format[CanCalculateChanges] = Json.valueFormat[CanCalculateChanges]
 
   private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
-  private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
   private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
 
   private implicit val sortPropertyReads: Reads[SortProperty] = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
index f8580c9..e5bc83b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
@@ -30,7 +30,7 @@
 
 object MailboxQuerySerializer {
   private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
-  private implicit val canCalculateChangeWrites: Writes[CanCalculateChange] = Json.valueWrites[CanCalculateChange]
+  private implicit val canCalculateChangeWrites: Writes[CanCalculateChanges] = Json.valueWrites[CanCalculateChanges]
 
   private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
 
@@ -44,7 +44,6 @@
   private implicit val emailQueryRequestReads: Reads[MailboxQueryRequest] = Json.reads[MailboxQueryRequest]
   private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
-  private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
 
   private implicit def mailboxQueryResponseWrites: OWrites[MailboxQueryResponse] = Json.writes[MailboxQueryResponse]
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index f5b282e..8f23532 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
@@ -18,11 +18,9 @@
  ****************************************************************/
 
 package org.apache.james.jmap.mail
-
-import org.apache.james.jmap.model.{Keyword, UTCDate}
-import com.google.common.hash.Hashing
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, LimitUnparsed, Position, QueryState, UTCDate}
 import org.apache.james.jmap.mail.IsAscending.{ASCENDING, DESCENDING}
-import org.apache.james.jmap.model.AccountId
+import org.apache.james.jmap.model.Limit.Limit
 import org.apache.james.mailbox.model.SearchQuery.Sort.Order.{NATURAL, REVERSE}
 import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
 import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery}
@@ -34,17 +32,7 @@
                            hasKeyword: Option[Keyword],
                            notKeyword: Option[Keyword])
 
-case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition], comparator: Option[Set[Comparator]])
-
-case class Position(value: Int) extends AnyVal
-object Position{
-  val zero: Position = Position(0)
-}
-case class Limit(value: Long) extends AnyVal
-object Limit {
-  val default: Limit = Limit(256L)
-}
-case class QueryState(value: String) extends AnyVal
+case class EmailQueryRequest(accountId: AccountId, limit: Option[LimitUnparsed], filter: Option[FilterCondition], comparator: Option[Set[Comparator]])
 
 sealed trait SortProperty {
   def toSortClause: SortClause
@@ -77,19 +65,6 @@
   def toSort: SearchQuery.Sort = new SearchQuery.Sort(property.toSortClause, isAscending.getOrElse(ASCENDING).toSortOrder)
 }
 
-object QueryState {
-  def forIds(ids: Seq[MessageId]): QueryState = QueryState(
-    Hashing.murmur3_32()
-      .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
-      .toString)
-}
-
-object CanCalculateChanges {
-  val CANT: CanCalculateChanges = CanCalculateChanges(false)
-}
-
-case class CanCalculateChanges(value: Boolean) extends AnyVal
-
 case class EmailQueryResponse(accountId: AccountId,
                               queryState: QueryState,
                               canCalculateChanges: CanCalculateChanges,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
index 56a7e62..6f9df68 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
@@ -19,7 +19,8 @@
 
 package org.apache.james.jmap.mail
 
-import org.apache.james.jmap.model.{AccountId, CanCalculateChange, Limit, Position, QueryState}
+import org.apache.james.jmap.model.Limit.Limit
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Position, QueryState}
 import org.apache.james.mailbox.Role
 import org.apache.james.mailbox.model.MailboxId
 
@@ -29,7 +30,7 @@
 
 case class MailboxQueryResponse(accountId: AccountId,
                               queryState: QueryState,
-                              canCalculateChanges: CanCalculateChange,
+                              canCalculateChanges: CanCalculateChanges,
                               ids: Seq[MailboxId],
                               position: Position,
                               limit: Option[Limit])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index ab5ddb8..7437260 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -21,11 +21,12 @@
 import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.{CanCalculateChanges, Comparator, EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
+import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, EmailQueryResponse}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.model.{Capabilities, ErrorCode, Invocation}
+import org.apache.james.jmap.model.{CanCalculateChanges, Capabilities, ErrorCode, Invocation, Limit, Position, QueryState}
+import org.apache.james.jmap.model.Limit.Limit
 import org.apache.james.jmap.routes.ProcessingContext
 import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
 import org.apache.james.jmap.utils.search.MailboxFilter
@@ -58,16 +59,21 @@
 
   private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: EmailQueryRequest): SMono[Invocation] = {
     val searchQuery: MultimailboxesSearchQuery = searchQueryFromRequest(request)
+    for {
+      limitToUse <- Limit.validateRequestLimit(request.limit)
+      response <- executeQuery(mailboxSession, request, searchQuery, limitToUse)
+    } yield Invocation(methodName = methodName, arguments = Arguments(serializer.serialize(response)), methodCallId = invocation.methodCallId)
+  }
 
-    SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, Limit.default.value))
+  private def executeQuery(mailboxSession: MailboxSession, request: EmailQueryRequest, searchQuery: MultimailboxesSearchQuery, limitToUse: Limit): SMono[EmailQueryResponse] = {
+    SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, limitToUse))
       .collectSeq()
       .map(ids => EmailQueryResponse(accountId = request.accountId,
         queryState = QueryState.forIds(ids),
-        canCalculateChanges = CanCalculateChanges.CANT,
+        canCalculateChanges = CanCalculateChanges.CANNOT,
         ids = ids,
         position = Position.zero,
-        limit = Some(Limit.default)))
-      .map(response => Invocation(methodName = methodName, arguments = Arguments(serializer.serialize(response)), methodCallId = invocation.methodCallId))
+        limit = Some(limitToUse).filterNot(used => request.limit.map(_.value).contains(used.value))))
   }
 
   private def searchQueryFromRequest(request: EmailQueryRequest): MultimailboxesSearchQuery = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
index a85b657..46e9417 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
@@ -59,7 +59,7 @@
       .collectSeq()
       .map(ids => MailboxQueryResponse(accountId = request.accountId,
         queryState = QueryState.forMailboxIds(ids),
-        canCalculateChanges = CanCalculateChange(false),
+        canCalculateChanges = CanCalculateChanges(false),
         ids = ids,
         position = Position.zero,
         limit = Some(Limit.default)))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
index 24135dd..624e3e0 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
@@ -20,15 +20,33 @@
 package org.apache.james.jmap.model
 
 import com.google.common.hash.Hashing
+import eu.timepit.refined.auto._
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.numeric.Positive
+import eu.timepit.refined.refineV
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import reactor.core.scala.publisher.SMono
 
 case class Position(value: Int) extends AnyVal
 object Position{
   val zero: Position = Position(0)
 }
-case class Limit(value: Long) extends AnyVal
+
+case class LimitUnparsed(value: Long) extends AnyVal
+
 object Limit {
-  val default: Limit = Limit(256L)
+  type Limit = Long Refined Positive
+  val default: Limit = 256L
+
+  def validateRequestLimit(requestLimit: Option[LimitUnparsed]): SMono[Limit] = {
+    val refinedLimit : Option[Either[String, Limit]] =  requestLimit.map(limit => refineV[Positive](limit.value))
+
+    refinedLimit match {
+      case Some(Left(_))  =>  SMono.raiseError(new IllegalArgumentException(s"The limit can not be negative. ${requestLimit.map(_.value).getOrElse("")} was provided."))
+      case Some(Right(limit)) if limit.value < default.value => SMono.just(limit)
+      case _ => SMono.just(default)
+    }
+  }
 }
 
 case class QueryState(value: String) extends AnyVal
@@ -45,4 +63,8 @@
       .toString)
 }
 
-case class CanCalculateChange(value: Boolean) extends AnyVal
+case class CanCalculateChanges(value: Boolean) extends AnyVal
+
+object CanCalculateChanges {
+  val CANNOT: CanCalculateChanges = CanCalculateChanges(false)
+}