Repository: softwaremill/elasticmq
Pull request #403 (2020-10-20 15:22:30)

Graalvm image building


Patch:
diff --git a/README.md b/README.md
index 76b5fa7d..1d6576f1 100644
--- a/README.md
+++ b/README.md
@@ -154,8 +154,6 @@ All attributes are optional (except `name` and `maxReceiveCount` when a `deadLet
 `copyTo` and `moveTo` attributes allow to achieve behavior that might be useful primarily for integration testing scenarios -
 all messages could be either duplicated (using `copyTo` attribute) or redirected (using `moveTo` attribute) to another queue.
 
-While creating the FIFO queue, .fifo suffix will be added automatically to queue name.
-
 Starting an embedded ElasticMQ server with an SQS interface
 -----------------------------------------------------------
 
@@ -292,6 +290,19 @@ docker run -p 9324:9324 --rm -it softwaremill/elasticmq-native
 The `native-elasticmq` image is much smaller (30MB vs 240MB) and starts up much faster (milliseconds instead of seconds).
 However, it's an experimental feature, so some things might not work.
 
+When building native image, do not forget to adjust the CPU and memory settings for the Docker process. It was checked with 6CPUs, 8GB of memory and 2GB of swap. To rebuild the native image, run:
+
+```
+sbt "project nativeServer; clean; graalvm-native-image:packageBin; docker:publishLocal"
+```
+
+Generating GraalVM config files is a manual process currently. You need to run the fat-jar using the GraalVM VM (w/ native-image installed using `gu`), and then run the following commands to generate the configs:
+
+* `java -agentlib:native-image-agent=config-output-dir=... -jar elasticmq-server-assembly.jar`
+* `java -agentlib:native-image-agent=config-merge-dir=... -Dconfig.file=test.conf -jar elasticmq-server-assembly.jar` (to additionally generate config needed to load custom elasticmq config)
+
+These files should be placed in `native-server/src/main/resources/META-INF/native-image` and are automatically used by the native-image process.
+
 ElasticMQ dependencies in SBT
 -----------------------------
 
diff --git a/build.sbt b/build.sbt
index a1bf8c5d..b48e02cb 100644
--- a/build.sbt
+++ b/build.sbt
@@ -10,8 +10,8 @@ val v2_13 = "2.13.3"
 
 val buildSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq(
   organization := "org.elasticmq",
-  scalaVersion := v2_12,
-  crossScalaVersions := Seq(v2_12, v2_13),
+  scalaVersion := v2_13,
+  crossScalaVersions := Seq(v2_13, v2_12),
   libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.3.0",
   dependencyOverrides := akka25Overrides,
   parallelExecution := false,
@@ -84,8 +84,8 @@ val awaitility = "org.awaitility" % "awaitility-scala" % "4.0.3"
 
 val amazonJavaSdk = "com.amazonaws" % "aws-java-sdk" % "1.11.848" exclude ("commons-logging", "commons-logging")
 
-val akkaVersion = "2.6.10"
-val akkaHttpVersion = "10.2.1"
+val akkaVersion = "2.6.8"
+val akkaHttpVersion = "10.2.0"
 val akka2Actor = "com.typesafe.akka" %% "akka-actor" % akkaVersion
 val akka2Slf4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion
 val akka2Streams = "com.typesafe.akka" %% "akka-stream" % akkaVersion
@@ -221,28 +221,30 @@ lazy val nativeServer: Project = (project in file("native-server"))
   .settings(buildSettings)
   .settings(Seq(
     name := "elasticmq-native-server",
-    libraryDependencies += "org.graalvm.nativeimage" % "svm" % graalVmVersion % Provided,
-    mainClass in Compile := Some("org.elasticmq.server.Main"),
+    libraryDependencies ++= Seq(
+      "org.graalvm.nativeimage" % "svm" % graalVmVersion % "compile-internal"
+    ),
     //configures sbt-native-packager to build app using dockerized graalvm
-    graalVMNativeImageGraalVersion := Some(graalVmVersion),
+    graalVMNativeImageGraalVersion := Some(graalVmVersion + "-java11"),
     graalVMNativeImageOptions ++= Seq(
       "--static",
-      "-H:ReflectionConfigurationFiles=" + "/opt/graalvm/stage/resources/reflectconf/logback.json",
-      "-H:ReflectionConfigurationFiles=" + "/opt/graalvm/stage/resources/reflectconf/akka.json",
-      "-H:ReflectionConfigurationFiles=" + "/opt/graalvm/stage/resources/reflectconf/typesafe-config.json",
       "-H:IncludeResources=.*conf",
       "-H:IncludeResources=version",
       "-H:IncludeResources=.*\\.properties",
       "-H:IncludeResources='org/joda/time/tz/data/.*'",
       "-H:+ReportExceptionStackTraces",
+      "-H:-ThrowUnsafeOffsetErrors",
+      "-H:+PrintClassInitialization",
       "--enable-http",
       "--enable-https",
       "--enable-url-protocols=https,http",
       "--initialize-at-build-time",
       "--report-unsupported-elements-at-runtime",
       "--allow-incomplete-classpath",
-      "--no-fallback"
+      "--no-fallback",
+      "--verbose"
     ),
+    mainClass in Compile := Some("org.elasticmq.server.Main"),
     //configures sbt-native-packager to build docker image with generated executable
     dockerBaseImage := "alpine:3.11",
     mappings in Docker := Seq(
@@ -255,7 +257,8 @@ lazy val nativeServer: Project = (project in file("native-server"))
     dockerCommands := {
       val commands = dockerCommands.value
       val index = commands.indexWhere {
-        case Cmd("FROM", args@_*) => args.size == 1
+        case Cmd("FROM", args@_*) =>
+          args.head == "alpine:3.11" && args.last == "mainstage"
         case _ => false
       }
       val (front, back) = commands.splitAt(index + 1)
diff --git a/core/src/main/scala/org/elasticmq/ElasticMQError.scala b/core/src/main/scala/org/elasticmq/ElasticMQError.scala
index 46d2c8b9..265c29fe 100644
--- a/core/src/main/scala/org/elasticmq/ElasticMQError.scala
+++ b/core/src/main/scala/org/elasticmq/ElasticMQError.scala
@@ -11,11 +11,6 @@ class QueueAlreadyExists(val queueName: String) extends ElasticMQError {
   val message = s"Queue already exists: $queueName"
 }
 
-case class QueueCreationError(queueName: String, reason: String) extends ElasticMQError {
-  val code = "QueueCreationError"
-  val message = s"Queue named $queueName could not be created because of $reason"
-}
-
 class MessageDoesNotExist(val queueName: String, messageId: MessageId) extends ElasticMQError {
   val code = "MessageDoesNotExist"
   val message = s"Message does not exist: $messageId in queue: $queueName"
diff --git a/core/src/main/scala/org/elasticmq/Limits.scala b/core/src/main/scala/org/elasticmq/Limits.scala
deleted file mode 100644
index d6bd95bc..00000000
--- a/core/src/main/scala/org/elasticmq/Limits.scala
+++ /dev/null
@@ -1,127 +0,0 @@
-package org.elasticmq
-
-import scala.collection.JavaConverters._
-import scala.util.Try
-
-trait Limits {
-  def queueNameLengthLimit: Limit[Int]
-  def batchSizeLimit: Limit[Int]
-  def numberOfMessagesLimit: Limit[RangeLimit[Int]]
-  def bodyValidCharactersLimit: Limit[List[RangeLimit[Int]]]
-  def numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]]
-  def messageWaitTimeLimit: Limit[RangeLimit[Long]]
-  def maxMessageLength: Limit[Int]
-}
-
-case object StrictSQSLimits extends Limits {
-  override val queueNameLengthLimit: Limit[Int] = LimitedValue(80)
-  override val batchSizeLimit: Limit[Int] = LimitedValue(10)
-  override val numberOfMessagesLimit: Limit[RangeLimit[Int]] = LimitedValue(RangeLimit(1, 10))
-  override val bodyValidCharactersLimit: Limit[List[RangeLimit[Int]]] = LimitedValue(
-    List(
-      RangeLimit(0x9, 0x9),
-      RangeLimit(0xa, 0xa),
-      RangeLimit(0xd, 0xd),
-      RangeLimit(0x20, 0xd7ff),
-      RangeLimit(0xe000, 0xfffd),
-      RangeLimit(0x10000, 0x10ffff)
-    )
-  )
-  override val numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]] = LimitedValue(
-    RangeLimit(-BigDecimal.valueOf(10).pow(128), BigDecimal.valueOf(10).pow(126))
-  )
-  override val messageWaitTimeLimit: Limit[RangeLimit[Long]] = LimitedValue(RangeLimit(0L, 20L))
-  override val maxMessageLength: Limit[Int] = LimitedValue(262144)
-}
-case object RelaxedSQSLimits extends Limits {
-  override val queueNameLengthLimit: Limit[Int] = NoLimit
-  override val batchSizeLimit: Limit[Int] = NoLimit
-  override val numberOfMessagesLimit: Limit[RangeLimit[Int]] = NoLimit
-  override val bodyValidCharactersLimit: Limit[List[RangeLimit[Int]]] = NoLimit
-  override val numberAttributeValueLimit: Limit[RangeLimit[BigDecimal]] = NoLimit
-  override val messageWaitTimeLimit: Limit[RangeLimit[Long]] = NoLimit
-  override val maxMessageLength: Limit[Int] = NoLimit
-}
-
-sealed trait Limit[+A]
-case class LimitedValue[A](value: A) extends Limit[A]
-case object NoLimit extends Limit[Nothing]
-
-case class RangeLimit[A](from: A, to: A)(implicit ord: Ordering[A]) {
-  def isBetween(value: A): Boolean = ord.gteq(value, from) && ord.lteq(value, to)
-}
-
-object Limits {
-
-  def verifyBatchSize(batchSize: Int, limits: Limits): Either[String, Unit] =
-    validateWhenLimitAvailable(limits.batchSizeLimit)(
-      limit => batchSize <= limit,
-      "AWS.SimpleQueueService.TooManyEntriesInBatchRequest"
-    )
-
-  def verifyNumberOfMessagesFromParameters(
-      numberOfMessagesFromParameters: Int,
-      limits: Limits
-  ): Either[String, Unit] =
-    validateWhenLimitAvailable(limits.numberOfMessagesLimit)(
-      limit => limit.isBetween(numberOfMessagesFromParameters),
-      "ReadCountOutOfRange"
-    )
-
-  def verifyMessageStringAttribute(stringAttribute: String, limits: Limits): Either[String, Unit] =
-    for {
-      _ <- validateWhenLimitAvailable(limits.bodyValidCharactersLimit)(
-        rangeLimits =>
-          stringAttribute
-            .codePoints()
-            .iterator
-            .asScala
-            .forall(codePoint => rangeLimits.exists(range => range.isBetween(codePoint))),
-        "InvalidMessageContents"
-      )
-      _ <- verifyMessageLength(stringAttribute.length, limits)
-    } yield ()
-
-  def verifyMessageNumberAttribute(stringNumberValue: String, limits: Limits): Either[String, Unit] = {
-    validateWhenLimitAvailable(limits.numberAttributeValueLimit)(
-      limit => Try(BigDecimal(stringNumberValue)).toOption.exists(value => limit.isBetween(value)),
-      s"Number attribute value $stringNumberValue should be in range (-10**128..10**126)"
-    )
-  }
-
-  def verifyMessageWaitTime(messageWaitTime: Long, limits: Limits): Either[String, Unit] = {
-    for {
-      _ <- if (messageWaitTime < 0) Left("InvalidParameterValue") else Right(())
-      _ <- validateWhenLimitAvailable(limits.messageWaitTimeLimit)(
-        limit => limit.isBetween(messageWaitTime),
-        "InvalidParameterValue"
-      )
-    } yield ()
-  }
-
-  def verifyMessageLength(messageLength: Int, limits: Limits): Either[String, Unit] = {
-    validateWhenLimitAvailable(limits.maxMessageLength)(
-      limit => messageLength <= limit,
-      "MessageTooLong"
-    )
-  }
-
-  def verifyQueueName(queueName: String, isFifo: Boolean, limits: Limits): Either[String, Unit] = {
-    for {
-      _ <- if (!queueName.matches("[\\p{Alnum}\\._-]*")) Left("InvalidParameterValue") else Right(())
-      _ <- if (isFifo && !queueName.endsWith(".fifo")) Left("InvalidParameterValue") else Right(())
-      _ <- validateWhenLimitAvailable(limits.queueNameLengthLimit)(
-        limit => queueName.length <= limit,
-        "InvalidParameterValue"
-      )
-    } yield ()
-  }
-
-  private def validateWhenLimitAvailable[LimitValue](
-      limit: Limit[LimitValue]
-  )(validation: LimitValue => Boolean, error: String): Either[String, Unit] =
-    limit match {
-      case LimitedValue(value) => if (!validation(value)) Left(error) else Right(())
-      case NoLimit             => Right(())
-    }
-}
diff --git a/core/src/main/scala/org/elasticmq/actor/QueueManagerActor.scala b/core/src/main/scala/org/elasticmq/actor/QueueManagerActor.scala
index d1e30b0e..433d3adb 100644
--- a/core/src/main/scala/org/elasticmq/actor/QueueManagerActor.scala
+++ b/core/src/main/scala/org/elasticmq/actor/QueueManagerActor.scala
@@ -5,11 +5,11 @@ import org.elasticmq.actor.queue.QueueActor
 import org.elasticmq.actor.reply._
 import org.elasticmq.msg.{CreateQueue, DeleteQueue, _}
 import org.elasticmq.util.{Logging, NowProvider}
-import org.elasticmq._
+import org.elasticmq.{QueueAlreadyExists, QueueData}
 
 import scala.reflect._
 
-class QueueManagerActor(nowProvider: NowProvider, limits: Limits) extends ReplyingActor with Logging {
+class QueueManagerActor(nowProvider: NowProvider) extends ReplyingActor with Logging {
   type M[X] = QueueManagerMsg[X]
   val ev: ClassTag[QueueManagerMsg[Unit]] = classTag[M[Unit]]
 
@@ -23,14 +23,9 @@ class QueueManagerActor(nowProvider: NowProvider, limits: Limits) extends Replyi
           Left(new QueueAlreadyExists(queueData.name))
         } else {
           logger.info(s"Creating queue $queueData")
-          Limits.verifyQueueName(queueData.name, queueData.isFifo, limits) match {
-            case Left(error) =>
-              Left(QueueCreationError(queueData.name, error))
-            case Right(_) =>
-              val actor = createQueueActor(nowProvider, queueData)
-              queues(queueData.name) = actor
-              Right(actor)
-          }
+          val actor = createQueueActor(nowProvider, queueData)
+          queues(queueData.name) = actor
+          Right(actor)
         }
 
       case DeleteQueue(queueName) =>
@@ -53,13 +48,7 @@ class QueueManagerActor(nowProvider: NowProvider, limits: Limits) extends Replyi
 
     context.actorOf(
       Props(
-        new QueueActor(
-          nowProvider,
-          queueData,
-          deadLetterQueueActor,
-          copyMessagesToQueueActor,
-          moveMessagesToQueueActor
-        )
+        new QueueActor(nowProvider, queueData, deadLetterQueueActor, copyMessagesToQueueActor, moveMessagesToQueueActor)
       )
     )
   }
diff --git a/core/src/main/scala/org/elasticmq/actor/queue/QueueActorMessageOps.scala b/core/src/main/scala/org/elasticmq/actor/queue/QueueActorMessageOps.scala
index 5f807081..0a225c96 100644
--- a/core/src/main/scala/org/elasticmq/actor/queue/QueueActorMessageOps.scala
+++ b/core/src/main/scala/org/elasticmq/actor/queue/QueueActorMessageOps.scala
@@ -22,7 +22,6 @@ trait QueueActorMessageOps extends Logging {
         receiveMessages(visibilityTimeout, count, receiveRequestAttemptId)
       case DeleteMessage(deliveryReceipt) => deleteMessage(deliveryReceipt)
       case LookupMessage(messageId)       => messageQueue.byId.get(messageId.id).map(_.toMessageData)
-      case MoveMessage(message)           => moveMessage(message)
     }
 
   private def handleOrRedirectMessage(message: NewMessageData): ReplyAction[MessageData] = {
@@ -124,7 +123,7 @@ trait QueueActorMessageOps extends Logging {
           case default => Some(default)
         }
       })
-      .getOrElse(getMessagesFromQueue(count))
+      .getOrElse(getMessagesFromQueue(visibilityTimeout, count))
       .map { internalMessage =>
         // Putting the msg again into the queue, with a new next delivery
         val newNextDelivery = computeNextDelivery(visibilityTimeout)
@@ -151,12 +150,12 @@ trait QueueActorMessageOps extends Logging {
     }
   }
 
-  private def getMessagesFromQueue(count: Int) = {
+  private def getMessagesFromQueue(visibilityTimeout: VisibilityTimeout, count: Int) = {
     val deliveryTime = nowProvider.nowMillis
     messageQueue.dequeue(count, deliveryTime).flatMap { internalMessage =>
       if (queueData.deadLettersQueue.map(_.maxReceiveCount).exists(_ <= internalMessage.receiveCount)) {
         logger.debug(s"${queueData.name}: send message $internalMessage to dead letters actor $deadLettersActorRef")
-        deadLettersActorRef.foreach(_ ! MoveMessage(internalMessage))
+        deadLettersActorRef.foreach(_ ! SendMessage(internalMessage.toNewMessageData))
         internalMessage.deliveryReceipts.foreach(dr => deleteMessage(DeliveryReceipt(dr)))
         None
       } else {
@@ -186,27 +185,4 @@ trait QueueActorMessageOps extends Logging {
       }
     }
   }
-
-  private def moveMessage(message: InternalMessage): Unit = {
-    def moveMessageToQueue(internalMessage: InternalMessage): Unit = {
-      messageQueue += internalMessage
-      logger.debug(s"Moved message with id ${internalMessage.id} to DLQ ${queueData.name}")
-    }
-
-    copyMessagesToActorRef.foreach { _ ! SendMessage(message.toNewMessageData) }
-
-    if (queueData.isFifo) {
-      // Ensure a message with the same deduplication id is not on the queue already. If the message is already on the
-      // queue do nothing.
-      // TODO: A message dedup id should be checked up to 5 mins after it has been received. If it has been deleted
-      // during that period, it should _still_ be used when deduplicating new messages. If there's a match with a
-      // deleted message (that was sent less than 5 minutes ago, the new message should not be added).
-      messageQueue.byId.values.find(isDuplicate(message.toNewMessageData, _)) match {
-        case Some(_) => ()
-        case None    => moveMessageToQueue(message)
-      }
-    } else {
-      moveMessageToQueue(message)
-    }
-  }
 }
diff --git a/core/src/main/scala/org/elasticmq/msg/QueueMsg.scala b/core/src/main/scala/org/elasticmq/msg/QueueMsg.scala
index 037df5ae..96284978 100644
--- a/core/src/main/scala/org/elasticmq/msg/QueueMsg.scala
+++ b/core/src/main/scala/org/elasticmq/msg/QueueMsg.scala
@@ -2,7 +2,6 @@ package org.elasticmq.msg
 
 import akka.actor.ActorRef
 import org.elasticmq._
-import org.elasticmq.actor.queue.InternalMessage
 import org.elasticmq.actor.reply.Replyable
 import org.joda.time.Duration
 
@@ -26,7 +25,6 @@ case class GetQueueStatistics(deliveryTime: Long) extends QueueQueueMsg[QueueSta
 case class ClearQueue() extends QueueQueueMsg[Unit]
 
 case class SendMessage(message: NewMessageData) extends QueueMessageMsg[MessageData]
-case class MoveMessage(message: InternalMessage) extends QueueMessageMsg[Unit]
 case class UpdateVisibilityTimeout(messageId: MessageId, visibilityTimeout: VisibilityTimeout)
     extends QueueMessageMsg[Either[MessageDoesNotExist, Unit]]
 case class ReceiveMessages(
diff --git a/native-server/src/graal/reflectconf/akka.json b/native-server/src/graal/reflectconf/akka.json
deleted file mode 100644
index b3c616ef..00000000
--- a/native-server/src/graal/reflectconf/akka.json
+++ /dev/null
@@ -1,291 +0,0 @@
-[
-  {
-    "name": "akka.actor.Actor",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.ActorCell",
-    "fields" : [
-      { "name" : "props", "allowWrite" : true }
-    ]
-  },
-  {
-    "name": "akka.actor.ActorLogging",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.DefaultSupervisorStrategy",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.LocalActorRefProvider",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.LocalActorRefProvider$Guardian",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.LocalActorRefProvider$SystemGuardian",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.Props$EmptyActor",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.BoundedControlAwareMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.BoundedDequeBasedMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.BoundedMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.ControlAwareMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.DequeBasedMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.MailboxType",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.MultipleConsumerSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.ProducesMessageQueue",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.RequiresMessageQueue",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.UnboundedControlAwareMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.UnboundedDequeBasedMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.UnboundedMailbox",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.UnboundedDequeBasedMailbox",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.UnboundedMailbox$MessageQueue",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.dispatch.UnboundedMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.DeadLetterListener",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.DefaultLoggingFilter",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.EventStreamUnsubscriber",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.LoggerMailbox",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.LoggerMailboxType",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.LoggerMessageQueueSemantics",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.Logging$DefaultLogger",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.Logging$StdOutLogger",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.http.impl.engine.client.PoolMasterActor",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.InetAddressDnsProvider",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.InetAddressDnsResolver",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.SelectionHandler",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.SimpleDnsManager",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.TcpIncomingConnection",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.TcpListener",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.TcpManager",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.io.TcpOutgoingConnection",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.routing.ConsistentHashingPool",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.routing.RoundRobinPool",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.routing.RoutedActorCell$RouterActorCreator",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "com.typesafe.sslconfig.ssl.DefaultHostnameVerifier",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "java.lang.String",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "java.net.Inet6AddressImpl",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "java.util.Date",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "scala.Product",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "scala.Serializable",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "scala.concurrent.Future$InternalCallbackExecutor$",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "sun.reflect.Reflection",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.slf4j.Slf4jLoggingFilter",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.event.slf4j.Slf4jLogger",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.actor.LightArrayRevolverScheduler",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  },
-  {
-    "name": "akka.stream.impl.fusing.ActorGraphInterpreter",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true,
-    "fields" : [
-      { "name" : "context", "allowWrite" : true },
-      { "name" : "self", "allowWrite" : true }
-    ]
-  },
-  {
-    "name": "akka.io.TcpConnection",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true,
-    "fields" : [
-      { "name" : "context", "allowWrite" : true },
-      { "name" : "self", "allowWrite" : true }
-    ]
-  }
-]
\ No newline at end of file
diff --git a/native-server/src/graal/reflectconf/logback.json b/native-server/src/graal/reflectconf/logback.json
deleted file mode 100644
index 53419474..00000000
--- a/native-server/src/graal/reflectconf/logback.json
+++ /dev/null
@@ -1,162 +0,0 @@
-[
-  {
-    "name": "ch.qos.logback.classic.pattern.DateConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.MessageConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ThrowableProxyConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.NopThrowableInformationConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ContextNameConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldYellowCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.LoggerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.ReplacingCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldBlueCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.CyanCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.RedCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.WhiteCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.PropertyConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.MethodOfCallerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.LevelConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.IdentityCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldWhiteCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.MarkerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldCyanCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldMagentaCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.RelativeTimeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.MagentaCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ClassOfCallerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.LineOfCallerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.FileOfCallerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldGreenCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.LocalSequenceNumberConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.YellowCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.color.HighlightingCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.GrayCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.MDCConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ClassOfCallerConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BoldRedCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.GreenCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.core.pattern.color.BlackCompositeConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.ThreadConverter",
-    "allDeclaredConstructors": true
-  },
-  {
-    "name": "ch.qos.logback.classic.pattern.LineSeparatorConverter",
-    "allDeclaredConstructors": true
-  }
-]
\ No newline at end of file
diff --git a/native-server/src/graal/reflectconf/typesafe-config.json b/native-server/src/graal/reflectconf/typesafe-config.json
deleted file mode 100644
index 01d3d30d..00000000
--- a/native-server/src/graal/reflectconf/typesafe-config.json
+++ /dev/null
@@ -1,7 +0,0 @@
-[
-  {
-    "name": "com.typesafe.sslconfig.ssl.NoopHostnameVerifier",
-    "allDeclaredConstructors": true,
-    "allPublicConstructors": true
-  }
-]
diff --git a/native-server/src/main/java/org/elasticmq/server/AkkaSubstitutions.java b/native-server/src/main/java/org/elasticmq/server/AkkaSubstitutions.java
deleted file mode 100644
index 70066e42..00000000
--- a/native-server/src/main/java/org/elasticmq/server/AkkaSubstitutions.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.elasticmq.server;
-
-import com.oracle.svm.core.annotate.Alias;
-import com.oracle.svm.core.annotate.RecomputeFieldValue;
-import com.oracle.svm.core.annotate.TargetClass;
-
-// see https://medium.com/graalvm/instant-netty-startup-using-graalvm-native-image-generation-ed6f14ff7692
-@TargetClass(className = "akka.actor.LightArrayRevolverScheduler$")
-final class Target_akka_actor_LightArrayRevolverScheduler$ {
-    @Alias
-    @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FieldOffset, declClassName = "akka.actor.LightArrayRevolverScheduler$TaskHolder", name = "task")
-    public long akka$actor$LightArrayRevolverScheduler$$taskOffset;
-}
-
-public class AkkaSubstitutions {
-}
\ No newline at end of file
diff --git a/native-server/src/main/java/org/elasticmq/server/ScalaSubstitutions.java b/native-server/src/main/java/org/elasticmq/server/ScalaSubstitutions.java
new file mode 100644
index 00000000..3d7453b9
--- /dev/null
+++ b/native-server/src/main/java/org/elasticmq/server/ScalaSubstitutions.java
@@ -0,0 +1,16 @@
+package org.elasticmq.server;
+
+import com.oracle.svm.core.annotate.Substitute;
+import com.oracle.svm.core.annotate.TargetClass;
+
+@TargetClass(className = "scala.runtime.Statics")
+final class Target_scala_runtime_Statics {
+
+    @Substitute
+    public static void releaseFence() {
+        UnsafeUtils.UNSAFE.storeFence();
+    }
+}
+
+public class ScalaSubstitutions {
+}
diff --git a/native-server/src/main/java/org/elasticmq/server/UnsafeUtils.java b/native-server/src/main/java/org/elasticmq/server/UnsafeUtils.java
new file mode 100644
index 00000000..5eda99fe
--- /dev/null
+++ b/native-server/src/main/java/org/elasticmq/server/UnsafeUtils.java
@@ -0,0 +1,17 @@
+package org.elasticmq.server;
+
+import java.lang.reflect.Field;
+
+class UnsafeUtils {
+    static final sun.misc.Unsafe UNSAFE;
+
+    static {
+        try {
+            Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
+            field.setAccessible(true);
+            UNSAFE = (sun.misc.Unsafe) field.get(null);
+        } catch (Throwable ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+}
\ No newline at end of file
diff --git a/native-server/src/main/resources/META-INF/native-image/jni-config.json b/native-server/src/main/resources/META-INF/native-image/jni-config.json
new file mode 100644
index 00000000..601c7151
--- /dev/null
+++ b/native-server/src/main/resources/META-INF/native-image/jni-config.json
@@ -0,0 +1,15 @@
+[
+{
+  "name":"java.lang.ClassLoader",
+  "methods":[
+    {"name":"getPlatformClassLoader","parameterTypes":[] }, 
+    {"name":"loadClass","parameterTypes":["java.lang.String"] }
+  ]
+},
+{
+  "name":"java.lang.ClassNotFoundException"
+},
+{
+  "name":"java.lang.NoSuchMethodError"
+}
+]
diff --git a/native-server/src/main/resources/META-INF/native-image/reflect-config.json b/native-server/src/main/resources/META-INF/native-image/reflect-config.json
new file mode 100644
index 00000000..d8dfa6be
--- /dev/null
+++ b/native-server/src/main/resources/META-INF/native-image/reflect-config.json
@@ -0,0 +1,454 @@
+[
+{
+  "name":"akka.actor.ActorCell",
+  "fields":[
+    {"name":"akka$actor$dungeon$Children$$_childrenRefsDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"akka$actor$dungeon$Children$$_functionRefsDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"akka$actor$dungeon$Children$$_nextNameDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"akka$actor$dungeon$Dispatch$$_mailboxDoNotCallMeDirectly", "allowUnsafeAccess":true}
+  ]
+},
+{
+  "name":"akka.actor.DefaultSupervisorStrategy",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"akka.actor.LightArrayRevolverScheduler",
+  "methods":[{"name":"<init>","parameterTypes":["com.typesafe.config.Config","akka.event.LoggingAdapter","java.util.concurrent.ThreadFactory"] }]
+},
+{
+  "name":"akka.actor.LightArrayRevolverScheduler$TaskHolder",
+  "fields":[{"name":"task", "allowUnsafeAccess":true}]
+},
+{
+  "name":"akka.actor.LightArrayRevolverScheduler$TaskQueue[]"
+},
+{
+  "name":"akka.actor.LocalActorRefProvider",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.String","akka.actor.ActorSystem$Settings","akka.event.EventStream","akka.actor.DynamicAccess"] }]
+},
+{
+  "name":"akka.actor.LocalActorRefProvider$Guardian",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.actor.LocalActorRefProvider$SystemGuardian",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.actor.Props$EmptyActor",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"akka.actor.RepointableActorRef",
+  "fields":[
+    {"name":"_cellDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"_lookupDoNotCallMeDirectly", "allowUnsafeAccess":true}
+  ]
+},
+{
+  "name":"akka.dispatch.AbstractNodeQueue",
+  "fields":[{"name":"_tailDoNotCallMeDirectly", "allowUnsafeAccess":true}]
+},
+{
+  "name":"akka.dispatch.AbstractNodeQueue$Node",
+  "fields":[{"name":"_nextDoNotCallMeDirectly", "allowUnsafeAccess":true}]
+},
+{
+  "name":"akka.dispatch.BoundedControlAwareMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.BoundedDequeBasedMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.BoundedMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.ControlAwareMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.DequeBasedMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.Mailbox",
+  "fields":[
+    {"name":"_statusDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"_systemQueueDoNotCallMeDirectly", "allowUnsafeAccess":true}
+  ]
+},
+{
+  "name":"akka.dispatch.MessageDispatcher",
+  "fields":[
+    {"name":"_inhabitantsDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"_shutdownScheduleDoNotCallMeDirectly", "allowUnsafeAccess":true}
+  ]
+},
+{
+  "name":"akka.dispatch.MultipleConsumerSemantics"
+},
+{
+  "name":"akka.dispatch.UnboundedControlAwareMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.UnboundedDequeBasedMessageQueueSemantics"
+},
+{
+  "name":"akka.dispatch.UnboundedMailbox",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ActorSystem$Settings","com.typesafe.config.Config"] }]
+},
+{
+  "name":"akka.dispatch.UnboundedMessageQueueSemantics"
+},
+{
+  "name":"akka.event.DeadLetterListener",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"akka.event.EventStreamUnsubscriber",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.event.LoggerMailboxType",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ActorSystem$Settings","com.typesafe.config.Config"] }]
+},
+{
+  "name":"akka.event.LoggerMessageQueueSemantics"
+},
+{
+  "name":"akka.event.slf4j.Slf4jLogger",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"akka.event.slf4j.Slf4jLoggingFilter",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ActorSystem$Settings","akka.event.EventStream"] }]
+},
+{
+  "name":"akka.http.DefaultParsingErrorHandler$",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"akka.http.impl.engine.client.PoolMasterActor",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"akka.io.SelectionHandler",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.io.TcpIncomingConnection",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.io.TcpListener",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.io.TcpManager",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.pattern.PromiseActorRef",
+  "fields":[
+    {"name":"_stateDoNotCallMeDirectly", "allowUnsafeAccess":true}, 
+    {"name":"_watchedByDoNotCallMeDirectly", "allowUnsafeAccess":true}
+  ]
+},
+{
+  "name":"akka.routing.ConsistentHashingPool",
+  "methods":[{"name":"<init>","parameterTypes":["com.typesafe.config.Config"] }]
+},
+{
+  "name":"akka.routing.RoundRobinPool",
+  "methods":[{"name":"<init>","parameterTypes":["com.typesafe.config.Config"] }]
+},
+{
+  "name":"akka.routing.RoutedActorCell$RouterActorCreator",
+  "allDeclaredConstructors":true
+},
+{
+  "name":"akka.serialization.BooleanSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.serialization.ByteArraySerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.serialization.ByteStringSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.serialization.DisabledJavaSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.serialization.IntSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.serialization.LongSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.serialization.SerializationExtension$",
+  "fields":[{"name":"MODULE$"}]
+},
+{
+  "name":"akka.serialization.StringSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.stream.SinkRef"
+},
+{
+  "name":"akka.stream.SourceRef"
+},
+{
+  "name":"akka.stream.SystemMaterializer$",
+  "fields":[{"name":"MODULE$"}]
+},
+{
+  "name":"akka.stream.impl.streamref.StreamRefsProtocol"
+},
+{
+  "name":"akka.stream.serialization.StreamRefSerializer",
+  "methods":[{"name":"<init>","parameterTypes":["akka.actor.ExtendedActorSystem"] }]
+},
+{
+  "name":"akka.stream.stage.GraphStageLogic[]"
+},
+{
+  "name":"akka.util.ByteString$ByteString1"
+},
+{
+  "name":"akka.util.ByteString$ByteString1C"
+},
+{
+  "name":"akka.util.ByteString$ByteStrings"
+},
+{
+  "name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder",
+  "allPublicMethods":true,
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.DateConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.LevelConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.LineSeparatorConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.LoggerConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.MessageConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.classic.pattern.ThreadConverter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"ch.qos.logback.core.ConsoleAppender",
+  "allPublicMethods":true,
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.TlsMasterSecretGenerator",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"java.io.FilePermission"
+},
+{
+  "name":"java.io.Serializable"
+},
+{
+  "name":"java.lang.Boolean"
+},
+{
+  "name":"java.lang.Class[]"
+},
+{
+  "name":"java.lang.Integer"
+},
+{
+  "name":"java.lang.Long"
+},
+{
+  "name":"java.lang.RuntimePermission"
+},
+{
+  "name":"java.lang.String",
+  "fields":[{"name":"value", "allowUnsafeAccess":true}]
+},
+{
+  "name":"java.lang.String[]"
+},
+{
+  "name":"java.lang.Throwable",
+  "methods":[{"name":"getSuppressed","parameterTypes":[] }]
+},
+{
+  "name":"java.lang.invoke.VarHandle"
+},
+{
+  "name":"java.net.NetPermission"
+},
+{
+  "name":"java.net.SocketPermission"
+},
+{
+  "name":"java.net.URLPermission",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
+},
+{
+  "name":"java.security.AlgorithmParametersSpi"
+},
+{
+  "name":"java.security.AllPermission"
+},
+{
+  "name":"java.security.KeyStoreSpi"
+},
+{
+  "name":"java.security.MessageDigestSpi"
+},
+{
+  "name":"java.security.SecureRandomParameters"
+},
+{
+  "name":"java.security.SecurityPermission"
+},
+{
+  "name":"java.util.PropertyPermission"
+},
+{
+  "name":"javax.management.ObjectName"
+},
+{
+  "name":"javax.security.auth.x500.X500Principal",
+  "fields":[{"name":"thisX500Name"}],
+  "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
+},
+{
+  "name":"scala.Boolean"
+},
+{
+  "name":"scala.Int"
+},
+{
+  "name":"scala.Long"
+},
+{
+  "name":"sun.misc.Unsafe",
+  "allDeclaredFields":true
+},
+{
+  "name":"sun.security.pkcs12.PKCS12KeyStore",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.JavaKeyStore$JKS",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.MD5",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.NativePRNG",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.SHA",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.SHA2$SHA256",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.X509Factory",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.rsa.RSAKeyFactory$Legacy",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.x509.AuthorityInfoAccessExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.AuthorityKeyIdentifierExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.BasicConstraintsExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.CRLDistributionPointsExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.CertificatePoliciesExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.ExtendedKeyUsageExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.IssuerAlternativeNameExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.KeyUsageExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.NetscapeCertTypeExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.PrivateKeyUsageExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.SubjectAlternativeNameExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.SubjectKeyIdentifierExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+}
+]
diff --git a/native-server/src/main/resources/META-INF/native-image/resource-config.json b/native-server/src/main/resources/META-INF/native-image/resource-config.json
new file mode 100644
index 00000000..a17fc00e
--- /dev/null
+++ b/native-server/src/main/resources/META-INF/native-image/resource-config.json
@@ -0,0 +1,15 @@
+{
+  "resources":[
+    {"pattern":"\\QMETA-INF/MANIFEST.MF\\E"}, 
+    {"pattern":"\\Qakka-http-version.conf\\E"}, 
+    {"pattern":"\\Qapplication.conf\\E"}, 
+    {"pattern":"\\Qlogback.xml\\E"}, 
+    {"pattern":"\\Qorg/joda/time/tz/data/Europe/Warsaw\\E"}, 
+    {"pattern":"\\Qorg/joda/time/tz/data/ZoneInfoMap\\E"}, 
+    {"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"}, 
+    {"pattern":"\\Qreference.conf\\E"}, 
+    {"pattern":"\\Qversion.conf\\E"}, 
+    {"pattern":"\\Qversion\\E"}
+  ],
+  "bundles":[]
+}
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 825864fd..3ab48a97 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -9,7 +9,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
 
 addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
 
-addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.6")
+addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.5")
 
 libraryDependencies += "com.amazonaws" % "aws-java-sdk-s3" % "1.11.601"
 
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala
index ce140f51..541e7325 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala
@@ -3,9 +3,6 @@ package org.elasticmq.rest.sqs
 import xml.NodeSeq
 import Constants.IdSubParameter
 import java.util.regex.Pattern
-
-import org.elasticmq.Limits
-
 import scala.concurrent.Future
 
 trait BatchRequestsModule {
@@ -20,7 +17,9 @@ trait BatchRequestsModule {
       throw new SQSException("AWS.SimpleQueueService.BatchEntryIdsNotDistinct")
     }
 
-    Limits.verifyBatchSize(uniqueIds.size, sqsLimits).fold(error => throw new SQSException(error), identity)
+    ifStrictLimits(uniqueIds.size > 10) {
+      "AWS.SimpleQueueService.TooManyEntriesInBatchRequest"
+    }
 
     subParameters
   }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala
index f017c28b..71fd6dae 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala
@@ -7,7 +7,7 @@ import org.elasticmq.rest.sqs.CreateQueueDirectives._
 import org.elasticmq.rest.sqs.ParametersUtil._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
 import org.elasticmq.rest.sqs.model.RedrivePolicy.BackwardCompatibleRedrivePolicy
-import org.elasticmq.{DeadLettersQueueData, MillisVisibilityTimeout, QueueData, Limits}
+import org.elasticmq.{DeadLettersQueueData, MillisVisibilityTimeout, QueueData}
 import org.joda.time.{DateTime, Duration}
 import spray.json.JsonParser.ParsingException
 import spray.json._
@@ -68,7 +68,6 @@ trait CreateQueueDirectives {
             val now = new DateTime()
             val isFifo = attributes.get("FifoQueue").contains("true")
             val hasContentBasedDeduplication = attributes.get("ContentBasedDeduplication").contains("true")
-
             val newQueueData = QueueData(
               queueName,
               MillisVisibilityTimeout.fromSeconds(secondsVisibilityTimeout),
@@ -82,11 +81,18 @@ trait CreateQueueDirectives {
               tags = tagNameAndValuesReader.read(p)
             )
 
-            secondsReceiveMessageWaitTimeOpt.foreach(messageWaitTime =>
-              Limits
-                .verifyMessageWaitTime(messageWaitTime, sqsLimits)
-                .fold(error => throw new SQSException(error), identity)
-            )
+            if (!queueName.matches("[\\p{Alnum}\\._-]*")) {
+              throw SQSException.invalidParameterValue
+            } else if (
+              sqsLimits == SQSLimits.Strict && queueName
+                .length() > 80
+            ) {
+              throw SQSException.invalidParameterValue
+            } else if (isFifo && !queueName.endsWith(".fifo")) {
+              throw SQSException.invalidParameterValue
+            }
+
+            verifyMessageWaitTime(secondsReceiveMessageWaitTimeOpt)
 
             val queueData = await(lookupOrCreateQueue(newQueueData))
 
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala
index 48da4a71..33e61974 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala
@@ -65,15 +65,11 @@ trait ReceiveMessageDirectives {
 
         val messageAttributeNames = getMessageAttributeNames(p)
 
-        Limits
-          .verifyNumberOfMessagesFromParameters(maxNumberOfMessagesFromParameters, sqsLimits)
-          .fold(error => throw new SQSException(error), identity)
-
-        waitTimeSecondsAttributeOpt.foreach(messageWaitTime =>
-          Limits
-            .verifyMessageWaitTime(messageWaitTime, sqsLimits)
-            .fold(error => throw new SQSException(error), identity)
-        )
+        ifStrictLimits(maxNumberOfMessagesFromParameters < 1 || maxNumberOfMessagesFromParameters > 10) {
+          "ReadCountOutOfRange"
+        }
+
+        verifyMessageWaitTime(waitTimeSecondsAttributeOpt)
 
         val msgsFuture = queueActor ? ReceiveMessages(
           visibilityTimeoutFromParameters,
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala
index b83ee2e0..c7d9668b 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala
@@ -18,10 +18,12 @@ import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.directives.{ElasticMQDirectives, UnmatchedActionRoutes}
 import org.elasticmq.util.{Logging, NowProvider}
 
+import scala.annotation.tailrec
 import scala.collection.immutable.TreeMap
 import scala.collection.mutable.ArrayBuffer
 import scala.concurrent.duration._
 import scala.concurrent.{Await, Future}
+import scala.util.control.Exception._
 import scala.util.control.NonFatal
 import scala.xml.{EntityRef, _}
 
@@ -41,7 +43,7 @@ object SQSRestServerBuilder
       9324,
       NodeAddress(),
       true,
-      StrictSQSLimits,
+      SQSLimits.Strict,
       "elasticmq",
       "000000000000"
     )
@@ -53,7 +55,7 @@ case class TheSQSRestServerBuilder(
     port: Int,
     serverAddress: NodeAddress,
     generateServerAddress: Boolean,
-    sqsLimits: Limits,
+    sqsLimits: SQSLimits.Value,
     _awsRegion: String,
     _awsAccountId: String
 ) extends Logging {
@@ -99,7 +101,7 @@ case class TheSQSRestServerBuilder(
     * @param _sqsLimits Should "real" SQS limits be used (strict), or should they be relaxed where possible (regarding
     *                   e.g. message size).
     */
-  def withSQSLimits(_sqsLimits: Limits) =
+  def withSQSLimits(_sqsLimits: SQSLimits.Value) =
     this.copy(sqsLimits = _sqsLimits)
 
   /**
@@ -250,7 +252,7 @@ case class TheSQSRestServerBuilder(
   }
 
   private def getOrCreateQueueManagerActor(actorSystem: ActorSystem) = {
-    providedQueueManagerActor.getOrElse(actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider(), sqsLimits))))
+    providedQueueManagerActor.getOrElse(actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider()))))
   }
 }
 
@@ -422,8 +424,83 @@ trait QueueURLModule {
   }
 }
 
+object SQSLimits extends Enumeration {
+  val Strict = Value
+  val Relaxed = Value
+}
+
 trait SQSLimitsModule {
-  def sqsLimits: Limits
+
+  val NUMBER_ATTR_MAX_VALUE = BigDecimal.valueOf(10).pow(126)
+  val NUMBER_ATTR_MIN_VALUE = -BigDecimal.valueOf(10).pow(128)
+
+  def sqsLimits: SQSLimits.Value
+
+  def ifStrictLimits(condition: => Boolean)(exception: String): Unit = {
+    if (sqsLimits == SQSLimits.Strict && condition) {
+      throw new SQSException(exception)
+    }
+  }
+
+  def verifyMessageStringAttribute(strValue: String): Unit = {
+    ifStrictLimits(bodyContainsInvalidCharacters(strValue)) {
+      "InvalidMessageContents"
+    }
+
+    verifyMessageStringNotTooLong(strValue.length)
+  }
+
+  def verifyMessageNumberAttribute(strValue: String): Unit = {
+    ifStrictLimits(
+      !allCatch
+        .opt(BigDecimal(strValue))
+        .filter(v => v >= NUMBER_ATTR_MIN_VALUE)
+        .exists(v => v <= NUMBER_ATTR_MAX_VALUE)
+    ) {
+      s"Number attribute value $strValue should be in range (-10**128..10**126)"
+    }
+  }
+
+  def verifyMessageWaitTime(messageWaitTimeOpt: Option[Long]): Unit = {
+    messageWaitTimeOpt.foreach { messageWaitTime =>
+      if (messageWaitTime < 0) {
+        throw SQSException.invalidParameterValue
+      }
+
+      ifStrictLimits(messageWaitTime > 20 || messageWaitTime < 0) {
+        InvalidParameterValueErrorName
+      }
+    }
+  }
+
+  def verifyMessageStringNotTooLong(messageLength: Int): Unit = {
+    ifStrictLimits(messageLength > 262144) {
+      "MessageTooLong"
+    }
+  }
+
+  private def bodyContainsInvalidCharacters(body: String) = {
+    val bodyLength = body.length
+
+    @tailrec
+    def findInvalidCharacter(offset: Int): Boolean = {
+      if (offset < bodyLength) {
+        val c = body.codePointAt(offset)
+
+        if (isAllowedCharacter(c))
+          findInvalidCharacter(offset + Character.charCount(c))
+        else true
+      } else {
+        false
+      }
+    }
+
+    // Allow chars: #x9 | #xA | #xD | [#x20 to #xD7FF] | [#xE000 to #xFFFD] | [#x10000 to #x10FFFF]
+    def isAllowedCharacter(c: Int): Boolean =
+      c == 0x9 || c == 0xa || c == 0xd || (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd) || (c >= 0x10000 && c <= 0x10ffff)
+
+    findInvalidCharacter(0)
+  }
 }
 
 class ElasticMQConfig {
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
index 171306a1..ba8efe87 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
@@ -74,16 +74,12 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
         case "String" =>
           val strValue =
             parameters("MessageAttribute." + i + ".Value.StringValue")
-          Limits
-            .verifyMessageStringAttribute(strValue, sqsLimits)
-            .fold(error => throw new SQSException(error), identity)
+          verifyMessageStringAttribute(strValue)
           StringMessageAttribute(strValue, customDataType)
         case "Number" =>
           val strValue =
             parameters("MessageAttribute." + i + ".Value.StringValue")
-          Limits
-            .verifyMessageNumberAttribute(strValue, sqsLimits)
-            .fold(error => throw new SQSException(error), identity)
+          verifyMessageNumberAttribute(strValue)
           NumberMessageAttribute(strValue, customDataType)
         case "Binary" =>
           BinaryMessageAttribute.fromBase64(parameters("MessageAttribute." + i + ".Value.BinaryValue"), customDataType)
@@ -99,7 +95,7 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
     val body = parameters(MessageBodyParameter)
     val messageAttributes = getMessageAttributes(parameters)
 
-    Limits.verifyMessageStringAttribute(body, sqsLimits).fold(error => throw new SQSException(error), identity)
+    verifyMessageStringAttribute(body)
 
     val messageGroupId = parameters.get(MessageGroupIdParameter) match {
       // MessageGroupId is only supported for FIFO queues
@@ -183,7 +179,7 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
   }
 
   def verifyMessageNotTooLong(messageLength: Int): Unit =
-    Limits.verifyMessageLength(messageLength, sqsLimits).fold(error => throw new SQSException(error), identity)
+    verifyMessageStringNotTooLong(messageLength)
 
   private def sha256Hash(text: String): String = {
     String.format(
diff --git a/server/src/main/scala/org/elasticmq/server/ElasticMQServer.scala b/server/src/main/scala/org/elasticmq/server/ElasticMQServer.scala
index e84d9480..1d1b747e 100644
--- a/server/src/main/scala/org/elasticmq/server/ElasticMQServer.scala
+++ b/server/src/main/scala/org/elasticmq/server/ElasticMQServer.scala
@@ -7,7 +7,7 @@ import org.elasticmq.actor.reply._
 import org.elasticmq.rest.sqs.{CreateQueueDirectives, SQSRestServer, TheSQSRestServerBuilder}
 import org.elasticmq.server.config.{CreateQueue, ElasticMQServerConfig}
 import org.elasticmq.util.{Logging, NowProvider}
-import org.elasticmq.{DeadLettersQueueData, ElasticMQError, MillisVisibilityTimeout, QueueData}
+import org.elasticmq.{DeadLettersQueueData, MillisVisibilityTimeout, QueueData}
 import org.joda.time.{DateTime, Duration}
 
 import scala.concurrent.Await
@@ -20,25 +20,18 @@ class ElasticMQServer(config: ElasticMQServerConfig) extends Logging {
     val queueManagerActor = createBase()
     val restServerOpt = optionallyStartRestSqs(queueManagerActor)
 
-    val shutdown = () => {
+    createQueues(queueManagerActor)
+
+    () => {
       restServerOpt.map(_.stopAndGetFuture())
       Await.result(actorSystem.terminate(), Inf)
     }
-
-    createQueues(queueManagerActor) match {
-      case Nil => ()
-      case errors =>
-        errors.foreach(error => logger.error(s"Could not start server because $error"))
-        shutdown()
-    }
-
-    shutdown
   }
 
   private def createBase(): ActorRef = {
     config.storage match {
       case config.InMemoryStorage =>
-        actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider(), config.restSqs.sqsLimits)))
+        actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider())))
     }
   }
 
@@ -65,18 +58,17 @@ class ElasticMQServer(config: ElasticMQServerConfig) extends Logging {
     }
   }
 
-  private def createQueues(queueManagerActor: ActorRef): List[ElasticMQError] = {
+  private def createQueues(queueManagerActor: ActorRef): Unit = {
     implicit val timeout = {
       import scala.concurrent.duration._
       Timeout(5.seconds)
     }
 
-    config.createQueues.flatMap(cq =>
-      Await
-        .result(queueManagerActor ? org.elasticmq.msg.CreateQueue(configToParams(cq, new DateTime)), timeout.duration)
-        .swap
-        .toOption
-    )
+    config.createQueues.foreach { cq =>
+      // Synchronously create queues since order matters
+      val f = queueManagerActor ? org.elasticmq.msg.CreateQueue(configToParams(cq, new DateTime))
+      Await.result(f, timeout.duration)
+    }
   }
 
   private def configToParams(cq: CreateQueue, now: DateTime): QueueData = {
diff --git a/server/src/main/scala/org/elasticmq/server/config/ElasticMQServerConfig.scala b/server/src/main/scala/org/elasticmq/server/config/ElasticMQServerConfig.scala
index 555be043..898f8d6d 100644
--- a/server/src/main/scala/org/elasticmq/server/config/ElasticMQServerConfig.scala
+++ b/server/src/main/scala/org/elasticmq/server/config/ElasticMQServerConfig.scala
@@ -3,11 +3,13 @@ package org.elasticmq.server.config
 import java.util.concurrent.TimeUnit
 
 import com.typesafe.config.{Config, ConfigObject}
-import org.elasticmq.{NodeAddress, RelaxedSQSLimits, StrictSQSLimits}
+import org.elasticmq.NodeAddress
+import org.elasticmq.rest.sqs.SQSLimits
 import org.elasticmq.server.QueueSorter
 import org.elasticmq.util.Logging
 
 class ElasticMQServerConfig(config: Config) extends Logging {
+
   // Configure main storage
 
   sealed trait Storage
@@ -48,9 +50,9 @@ class ElasticMQServerConfig(config: Config) extends Logging {
     val sqsLimits = {
       val name = subConfig.getString("sqs-limits")
       if ("relaxed".equalsIgnoreCase(name)) {
-        RelaxedSQSLimits
+        SQSLimits.Relaxed
       } else if ("strict".equalsIgnoreCase(name)) {
-        StrictSQSLimits
+        SQSLimits.Strict
       } else {
         throw new IllegalArgumentException("Unknown sqs-limits name: " + name)
       }
@@ -78,9 +80,8 @@ class ElasticMQServerConfig(config: Config) extends Logging {
       .map {
         case (n, v) =>
           val c = v.asInstanceOf[ConfigObject].toConfig
-          val isFifo = getOptionalBoolean(c, "fifo").getOrElse(false)
           CreateQueue(
-            name = addSuffixWhenFifoQueue(n, isFifo),
+            name = n,
             defaultVisibilityTimeoutSeconds = getOptionalDuration(c, "defaultVisibilityTimeout"),
             delaySeconds = getOptionalDuration(c, "delay"),
             receiveMessageWaitSeconds = getOptionalDuration(c, "receiveMessageWait"),
@@ -92,7 +93,7 @@ class ElasticMQServerConfig(config: Config) extends Logging {
                 )
               )
             } else None,
-            isFifo = isFifo,
+            isFifo = getOptionalBoolean(c, "fifo").getOrElse(false),
             hasContentBasedDeduplication = getOptionalBoolean(c, "contentBasedDeduplication").getOrElse(false),
             copyMessagesTo = getOptionalString(c, "copyTo"),
             moveMessagesTo = getOptionalString(c, "moveTo"),
@@ -108,9 +109,4 @@ class ElasticMQServerConfig(config: Config) extends Logging {
   val awsRegion: String = awsConfig.getString("region")
   val awsAccountId: String = awsConfig.getString("accountId")
 
-  private def addSuffixWhenFifoQueue(queueName: String, isFifo: Boolean): String = {
-    if (isFifo && !queueName.endsWith(".fifo")) queueName + ".fifo"
-    else queueName
-  }
-
 }


Test patch:
diff --git a/core/src/test/scala/org/elasticmq/LimitsTest.scala b/core/src/test/scala/org/elasticmq/LimitsTest.scala
deleted file mode 100644
index e25710af..00000000
--- a/core/src/test/scala/org/elasticmq/LimitsTest.scala
+++ /dev/null
@@ -1,235 +0,0 @@
-package org.elasticmq
-
-import java.math.MathContext
-
-import org.scalatest.EitherValues
-import org.scalatest.matchers.should.Matchers
-import org.scalatest.wordspec.AnyWordSpec
-
-import scala.util.Random
-
-class LimitsTest extends AnyWordSpec with Matchers with EitherValues {
-
-  "Validation of batch size limits in strict mode" should {
-    "pass if the size of the batch is less than the limit (10)" in {
-      Limits.verifyBatchSize(5, StrictSQSLimits) shouldBe Right(())
-    }
-
-    "fail if the size of the batch is greater than the limit (10)" in {
-      val error = Limits.verifyBatchSize(15, StrictSQSLimits).left.value
-      error shouldBe "AWS.SimpleQueueService.TooManyEntriesInBatchRequest"
-    }
-  }
-
-  "Validation of batch size limits in relaxed mode" should {
-    "always pass" in {
-      Limits.verifyBatchSize(-5, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyBatchSize(5, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyBatchSize(15, RelaxedSQSLimits) shouldBe Right(())
-    }
-  }
-
-  "Validation of number of messages from parameters in strict mode" should {
-    "pass if the number is between the limits (1-10)" in {
-      Limits.verifyNumberOfMessagesFromParameters(1, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyNumberOfMessagesFromParameters(5, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyNumberOfMessagesFromParameters(10, StrictSQSLimits) shouldBe Right(())
-    }
-
-    "fail the validation if the number is less than the lower bound" in {
-      val error = Limits.verifyNumberOfMessagesFromParameters(0, StrictSQSLimits).left.value
-      error shouldBe "ReadCountOutOfRange"
-    }
-
-    "fail the validation if the number is greater than the upper bound" in {
-      val error = Limits.verifyNumberOfMessagesFromParameters(15, StrictSQSLimits).left.value
-      error shouldBe "ReadCountOutOfRange"
-    }
-  }
-
-  "Validation of number of messages from parameters in relaxed mode" should {
-    "always pass the validation" in {
-      Limits.verifyNumberOfMessagesFromParameters(-5, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyNumberOfMessagesFromParameters(0, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyNumberOfMessagesFromParameters(5, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyNumberOfMessagesFromParameters(15, RelaxedSQSLimits) shouldBe Right(())
-    }
-  }
-
-  "Validation of message string attribute in strict mode" should {
-    "pass if string attribute contains only allowed characters" in {
-      val testString = List(0x9, 0xa, 0xd, 0x21, 0xe005, 0x10efff).map(_.toChar).mkString
-      Limits.verifyMessageStringAttribute(testString, StrictSQSLimits) shouldBe Right(())
-    }
-
-    "pass if the string is empty" in {
-      Limits.verifyMessageStringAttribute("", StrictSQSLimits) shouldBe Right(())
-    }
-
-    "fail if string contains any not allowed character" in {
-      val testString = List(0x9, 0xa, 0xd, 0x21, 0xe005, 0x19, 0x10efff).map(_.toChar).mkString
-      val error = Limits.verifyMessageStringAttribute(testString, StrictSQSLimits).left.value
-      error shouldBe "InvalidMessageContents"
-    }
-  }
-
-  "Validation of message string attribute in relaxed mode" should {
-    "pass if string attribute contains only allowed characters" in {
-      val testString = List(0x9, 0xa, 0xd, 0x21, 0xe005, 0x10efff).map(_.toChar).mkString
-      Limits.verifyMessageStringAttribute(testString, RelaxedSQSLimits) shouldBe Right(())
-    }
-
-    "pass if the string is empty" in {
-      Limits.verifyMessageStringAttribute("", RelaxedSQSLimits) shouldBe Right(())
-    }
-
-    "pass if string contains any not allowed character" in {
-      val testString = List(0x9, 0xa, 0xd, 0x21, 0xe005, 0x19, 0x10efff).map(_.toChar).mkString
-      Limits.verifyMessageStringAttribute(testString, RelaxedSQSLimits) shouldBe Right(())
-    }
-  }
-
-  "Validation of message number attribute in strict mode" should {
-    "pass if the number is between the limits (-10^128 - 10^126)" in {
-      Limits.verifyMessageNumberAttribute(BigDecimal(10).pow(126).toString(), StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute((-BigDecimal(10).pow(128)).toString(), StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute(BigDecimal(0).toString(), StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute(
-        BigDecimal(Random.nextDouble()).toString(),
-        StrictSQSLimits
-      ) shouldBe Right(
-        ()
-      )
-    }
-
-    "fail if the number is bigger than the upper bound" in {
-      val overUpperBound = BigDecimal(10, MathContext.UNLIMITED).pow(126) + BigDecimal(0.1)
-      val error = Limits.verifyMessageNumberAttribute(overUpperBound.toString, StrictSQSLimits)
-      error shouldBe Left(s"Number attribute value $overUpperBound should be in range (-10**128..10**126)")
-    }
-
-    "fail if the number is below the lower bound" in {
-      val belowLowerBound = -BigDecimal(10, MathContext.UNLIMITED).pow(128) - BigDecimal(0.1)
-      val error =
-        Limits.verifyMessageNumberAttribute(belowLowerBound.toString, StrictSQSLimits)
-      error shouldBe Left(s"Number attribute value $belowLowerBound should be in range (-10**128..10**126)")
-    }
-
-    "fail if the number can't be parsed" in {
-      val error = Limits.verifyMessageNumberAttribute("12312312a", StrictSQSLimits).left.value
-      error shouldBe s"Number attribute value 12312312a should be in range (-10**128..10**126)"
-    }
-  }
-
-  "Validation of message number attribute in relaxed mode" should {
-    "always pass the validation" in {
-      val belowLowerBound = -BigDecimal(10).pow(128) - BigDecimal(0.1)
-      val overUpperBound = BigDecimal(10).pow(126) + BigDecimal(0.1)
-      Limits.verifyMessageNumberAttribute(belowLowerBound.toString, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute(BigDecimal(10).pow(126).toString(), RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute((-BigDecimal(10).pow(128)).toString(), RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute(overUpperBound.toString, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageNumberAttribute("12312312a", RelaxedSQSLimits) shouldBe Right(())
-    }
-  }
-
-  "Validation of message wait time in strict mode" should {
-    "pass if the wait time is between the limit range (0-20)" in {
-      Limits.verifyMessageWaitTime(0, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageWaitTime(13, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageWaitTime(20, StrictSQSLimits) shouldBe Right(())
-    }
-
-    "fail if the number is below the lower bound" in {
-      val error = Limits.verifyMessageWaitTime(-1, StrictSQSLimits).left.value
-      error shouldBe "InvalidParameterValue"
-    }
-
-    "fail if the number is above the upper bound" in {
-      val error = Limits.verifyMessageWaitTime(21, StrictSQSLimits).left.value
-      error shouldBe "InvalidParameterValue"
-    }
-  }
-
-  "Validation of message wait time in relaxed mode" should {
-    "pass if the wait time is bigger than 0" in {
-      Limits.verifyMessageWaitTime(0, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageWaitTime(13, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageWaitTime(20, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageWaitTime(25, RelaxedSQSLimits) shouldBe Right(())
-    }
-
-    "fail if the wait time is lower than 0" in {
-      val error = Limits.verifyMessageWaitTime(-1, StrictSQSLimits).left.value
-      error shouldBe "InvalidParameterValue"
-    }
-  }
-
-  "Validation of message length in strict mode" should {
-    "pass if the length is smaller than the limit (262144)" in {
-      Limits.verifyMessageLength(-5, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(0, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(100, StrictSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(262144, StrictSQSLimits) shouldBe Right(())
-    }
-
-    "fail if the length is bigger than the limit" in {
-      val error = Limits.verifyMessageLength(300000, StrictSQSLimits).left.value
-      error shouldBe "MessageTooLong"
-    }
-  }
-
-  "Validation of message length in relaxed mode" should {
-    "always pass" in {
-      Limits.verifyMessageLength(-5, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(0, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(100, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(262143, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(262144, RelaxedSQSLimits) shouldBe Right(())
-      Limits.verifyMessageLength(300000, RelaxedSQSLimits) shouldBe Right(())
-    }
-  }
-
-  "Validation of queue name in strict mode" should {
-    "pass if queue name is made of alphanumeric characters and has length smaller than 80" in {
-      Limits.verifyQueueName("abc123.-_", isFifo = false, StrictSQSLimits) shouldBe Right(())
-    }
-
-    "fail if queue name contains invalid characters" in {
-      val error = Limits.verifyQueueName("invalid#characters&.fifo", isFifo = true, StrictSQSLimits).left.value
-      error shouldBe "InvalidParameterValue"
-    }
-
-    "fail if normal queue name exceeds 80 characters limit cap" in {
-      val error = Limits
-        .verifyQueueName(
-          "over80CharactersOver80CharactersOver80CharactersOver80CharactersOver80Characterss",
-          isFifo = false,
-          StrictSQSLimits
-        )
-        .left
-        .value
-      error shouldBe "InvalidParameterValue"
-    }
-  }
-
-  "Validation of queue name in relaxed mode" should {
-    "pass when queue name is made of alphanumeric characters" in {
-      Limits.verifyQueueName("abc123.-_", isFifo = false, RelaxedSQSLimits) shouldBe Right(())
-    }
-
-    "pass when normal queue name exceeds 80 characters limit cap" in {
-      Limits.verifyQueueName(
-        "over80CharactersOver80CharactersOver80CharactersOver80CharactersOver80Characterss",
-        isFifo = false,
-        RelaxedSQSLimits
-      ) shouldBe Right(())
-    }
-
-    "fail if queue name contains invalid characters" in {
-      val error =
-        Limits.verifyQueueName("invalid#characters&.fifo", isFifo = true, RelaxedSQSLimits).left.value
-      error shouldBe "InvalidParameterValue"
-    }
-  }
-}
diff --git a/core/src/test/scala/org/elasticmq/actor/test/QueueManagerForEachTest.scala b/core/src/test/scala/org/elasticmq/actor/test/QueueManagerForEachTest.scala
index a699f0b2..7b7296be 100644
--- a/core/src/test/scala/org/elasticmq/actor/test/QueueManagerForEachTest.scala
+++ b/core/src/test/scala/org/elasticmq/actor/test/QueueManagerForEachTest.scala
@@ -1,7 +1,6 @@
 package org.elasticmq.actor.test
 
 import akka.actor.{ActorRef, ActorSystem, Props}
-import org.elasticmq.StrictSQSLimits
 import org.elasticmq.actor.QueueManagerActor
 import org.scalatest.{BeforeAndAfterEach, Suite}
 
@@ -16,7 +15,7 @@ trait QueueManagerForEachTest extends BeforeAndAfterEach {
   override protected def beforeEach(): Unit = {
     super.beforeEach()
     nowProvider = new MutableNowProvider
-    queueManagerActor = system.actorOf(Props(new QueueManagerActor(nowProvider, StrictSQSLimits)))
+    queueManagerActor = system.actorOf(Props(new QueueManagerActor(nowProvider)))
   }
 
   override protected def afterEach(): Unit = {
diff --git a/performance-tests/src/test/scala/org/elasticmq/performance/LocalPerformanceTest.scala b/performance-tests/src/test/scala/org/elasticmq/performance/LocalPerformanceTest.scala
index c8754f41..b30ce4b7 100644
--- a/performance-tests/src/test/scala/org/elasticmq/performance/LocalPerformanceTest.scala
+++ b/performance-tests/src/test/scala/org/elasticmq/performance/LocalPerformanceTest.scala
@@ -46,7 +46,7 @@ object LocalPerformanceTest extends App {
       "Running test for [%s], iterations: %d, msgs in iteration: %d, thread count: %d."
         .format(name, iterations, msgsInIteration, threadCount))
 
-    mq.start(StrictSQSLimits)
+    mq.start()
 
     val took = timed {
       val threads = for (i <- 1 to threadCount) yield {
@@ -98,8 +98,8 @@ object LocalPerformanceTest extends App {
 
     implicit val timeout = Timeout(10000L, TimeUnit.MILLISECONDS)
 
-    override def start(sqsLimits: Limits) = {
-      val queueManagerActor = super.start(sqsLimits)
+    override def start() = {
+      val queueManagerActor = super.start()
 
       currentQueue = Await
         .result(
@@ -119,7 +119,7 @@ object LocalPerformanceTest extends App {
     }
 
     def sendMessage(m: String): Unit = {
-      Await.result(currentQueue ? SendMessage(NewMessageData(None, m, Map.empty, ImmediateNextDelivery, None, None, 0)),
+      Await.result(currentQueue ? SendMessage(NewMessageData(None, m, Map.empty, ImmediateNextDelivery, None, None)),
                    10.seconds)
     }
 
@@ -134,9 +134,9 @@ object LocalPerformanceTest extends App {
   trait MQ {
     var actorSystem: ActorSystem = _
 
-    def start(sqsLimits: Limits): ActorRef = {
+    def start(): ActorRef = {
       actorSystem = ActorSystem("performance-tests")
-      val queueManagerActor = actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider(), sqsLimits)))
+      val queueManagerActor = actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider())))
 
       queueManagerActor
     }
@@ -155,8 +155,8 @@ object LocalPerformanceTest extends App {
     private var currentQueueUrl: String = _
     private var currentRestServer: SQSRestServer = _
 
-    override def start(sqsLimits: Limits) = {
-      val queueManagerActor = super.start(sqsLimits)
+    override def start() = {
+      val queueManagerActor = super.start()
 
       currentRestServer = SQSRestServerBuilder
         .withActorSystem(actorSystem)
diff --git a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
index 4453f884..baa08449 100644
--- a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
+++ b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
@@ -58,7 +58,7 @@ class AmazonJavaSdkTestSuite extends AnyFunSuite with Matchers with BeforeAndAft
     relaxedServer = SQSRestServerBuilder
       .withPort(9322)
       .withServerAddress(NodeAddress(port = 9322))
-      .withSQSLimits(RelaxedSQSLimits)
+      .withSQSLimits(SQSLimits.Relaxed)
       .start()
 
     strictServer.waitUntilStarted()
@@ -1741,66 +1741,6 @@ class AmazonJavaSdkTestSuite extends AnyFunSuite with Matchers with BeforeAndAft
     newQueueAttributes(redrivePolicyAttribute) should be(redrivePolicy)
   }
 
-  test(
-    "When message is moved to dead letter queue, its SentTimestamp and ApproximateFirstReceiveTimestamp attributes should not be changed"
-  ) {
-    import org.elasticmq.rest.sqs.model.RedrivePolicyJson._
-    import spray.json._
-
-    def getOneMessage(queueUrl: String) = {
-      client
-        .receiveMessage(new ReceiveMessageRequest(queueUrl).withWaitTimeSeconds(2).withAttributeNames("All"))
-        .getMessages
-        .asScala
-        .headOption
-        .orNull
-    }
-
-    // Given
-    val deadLetterQueueUrl = client.createQueue(new CreateQueueRequest("dlq1")).getQueueUrl
-    val redrivePolicy = RedrivePolicy("dlq1", awsRegion, awsAccountId, 2).toJson.toString()
-    val createQueueResult = client.createQueue(
-      new CreateQueueRequest("q1")
-        .withAttributes(Map(defaultVisibilityTimeoutAttribute -> "1", redrivePolicyAttribute -> redrivePolicy).asJava)
-    )
-
-    // When
-
-    client.sendMessage(new SendMessageRequest(createQueueResult.getQueueUrl, "test message"))
-
-    val msg1 = getOneMessage(createQueueResult.getQueueUrl)
-    val msg2 = getOneMessage(createQueueResult.getQueueUrl)
-    val emptyMsg = getOneMessage(createQueueResult.getQueueUrl)
-
-    val msgFromDeadLetterQueue = getOneMessage(deadLetterQueueUrl)
-
-    // Then
-    val msg1SentTimestamp = msg1.getAttributes.get("SentTimestamp")
-    val msg1ApproximateReceiveCount = msg1.getAttributes.get("ApproximateReceiveCount")
-    val msg1ApproximateFirstReceiveTimestamp = msg1.getAttributes.get("SentTimestamp")
-
-    val msg2SentTimestamp = msg2.getAttributes.get("SentTimestamp")
-    val msg2ApproximateReceiveCount = msg2.getAttributes.get("ApproximateReceiveCount")
-    val msg2ApproximateFirstReceiveTimestamp = msg2.getAttributes.get("SentTimestamp")
-
-    val msgFromDeadLetterQueueSentTimestamp = msgFromDeadLetterQueue.getAttributes.get("SentTimestamp")
-    val msgFromDeadLetterQueueApproximateReceiveCount =
-      msgFromDeadLetterQueue.getAttributes.get("ApproximateReceiveCount")
-    val msgFromDeadLetterQueueApproximateFirstReceiveTimestamp =
-      msgFromDeadLetterQueue.getAttributes.get("SentTimestamp")
-
-    msg1SentTimestamp shouldBe msg2SentTimestamp
-    msg2SentTimestamp shouldBe msgFromDeadLetterQueueSentTimestamp
-
-    msg2ApproximateReceiveCount.toInt shouldBe (msg1ApproximateReceiveCount.toInt + 1)
-    msgFromDeadLetterQueueApproximateReceiveCount.toInt shouldBe (msg2ApproximateReceiveCount.toInt + 1)
-
-    msg1ApproximateFirstReceiveTimestamp shouldBe msg2ApproximateFirstReceiveTimestamp
-    msg2ApproximateFirstReceiveTimestamp shouldBe msgFromDeadLetterQueueApproximateFirstReceiveTimestamp
-
-    emptyMsg shouldBe null
-  }
-
   test("should return an error when the deadletter queue does not exist") {
     import org.elasticmq.rest.sqs.model.RedrivePolicyJson._
     import spray.json._
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/SQSStrictLimitsModuleTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/SQSStrictLimitsModuleTest.scala
new file mode 100644
index 00000000..862d0bdd
--- /dev/null
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/SQSStrictLimitsModuleTest.scala
@@ -0,0 +1,35 @@
+package org.elasticmq.rest.sqs
+
+import scala.util.Random
+import org.scalatest.funsuite.AnyFunSuite
+
+class SQSStrictLimitsModuleTest extends AnyFunSuite with SQSLimitsModule {
+
+  override def sqsLimits: SQSLimits.Value = SQSLimits.Strict
+
+  val correctValues =
+    List(BigDecimal(10).pow(126), -BigDecimal(10).pow(128), BigDecimal(0), BigDecimal(Random.nextDouble()))
+      .map(v => v.toString)
+  val incorrectValues =
+    List(
+      new java.math.BigDecimal(10).pow(126).add(new java.math.BigDecimal(0.1)).toString,
+      new java.math.BigDecimal(10).pow(128).subtract(new java.math.BigDecimal(0.1)).toString,
+      "12312312a"
+    )
+
+  for (value <- correctValues) {
+    test(s"should verify message number attribute value $value") {
+      verifyMessageNumberAttribute(value)
+    }
+  }
+
+  for (value <- incorrectValues) {
+    test(s"should throw an exception if number attribute value is out of range or NaN $value") {
+      val thrown = intercept[SQSException] {
+        verifyMessageNumberAttribute(value)
+      }
+      assert(thrown.message.startsWith(s"Number attribute value $value should be in range (-10**128..10**126)"))
+    }
+  }
+
+}
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala
index af47c928..92ccb756 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala
@@ -8,7 +8,7 @@ import org.elasticmq.actor.reply._
 import org.elasticmq.msg.CreateQueue
 import org.elasticmq.rest.sqs.{ActorSystemModule, QueueManagerActorModule}
 import org.elasticmq.util.NowProvider
-import org.elasticmq.{MillisVisibilityTimeout, QueueData, StrictSQSLimits}
+import org.elasticmq.{MillisVisibilityTimeout, QueueData}
 import org.joda.time.{DateTime, Duration}
 
 import scala.concurrent.{Await, ExecutionContextExecutor}
@@ -33,8 +33,7 @@ class QueueDirectivesTest
   implicit val timeout: Timeout = maxDuration
   implicit val ec: ExecutionContextExecutor = actorSystem.dispatcher
   implicit lazy val actorSystem: ActorSystem = ActorSystem("lol")
-  lazy val queueManagerActor: ActorRef =
-    actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider(), StrictSQSLimits)))
+  lazy val queueManagerActor: ActorRef = actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider())))
 
   "queueActorAndNameFromRequest" should "return correct queue name" in {
     val future = queueManagerActor ? CreateQueue(
diff --git a/server/src/test/scala/org/elasticmq/server/config/ElasticMQServerConfigTest.scala b/server/src/test/scala/org/elasticmq/server/config/ElasticMQServerConfigTest.scala
index 605db441..d600d201 100644
--- a/server/src/test/scala/org/elasticmq/server/config/ElasticMQServerConfigTest.scala
+++ b/server/src/test/scala/org/elasticmq/server/config/ElasticMQServerConfigTest.scala
@@ -1,11 +1,10 @@
 package org.elasticmq.server.config
 
 import com.typesafe.config.ConfigFactory
-import org.scalatest.OptionValues
 import org.scalatest.funsuite.AnyFunSuite
 import org.scalatest.matchers.should.Matchers
 
-class ElasticMQServerConfigTest extends AnyFunSuite with Matchers with OptionValues {
+class ElasticMQServerConfigTest extends AnyFunSuite with Matchers {
   test("load the default config") {
     // No exceptions -> good :)
     new ElasticMQServerConfig(ConfigFactory.load("conf/elasticmq"))
@@ -29,16 +28,4 @@ class ElasticMQServerConfigTest extends AnyFunSuite with Matchers with OptionVal
     conf.awsAccountId should be("1111111")
     conf.awsRegion should be("elastic")
   }
-
-  test("Normal queues should not have appended .fifo suffix") {
-    val conf = new ElasticMQServerConfig(ConfigFactory.load("test"))
-    val normalQueues = conf.createQueues.filter(!_.isFifo)
-    normalQueues.foreach(_.name should not endWith ".fifo")
-  }
-
-  test("FIFO queue should have appended .fifo suffix") {
-    val conf = new ElasticMQServerConfig(ConfigFactory.load("test"))
-    val fifoQueue = conf.createQueues.find(_.isFifo).value
-    fifoQueue.name shouldBe "fifoQueue.fifo"
-  }
 }