Clean up tags from a text
Beautify your regex
Say No to Pattern.compile and instead of

 Pattern.compile(&quot;^((?!(\\{.*\\})).)*$&quot;)


use scalish way:
 &quot;^((?!(\\{.*\\})).)*$&quot;.r

Say No to \\ in regex and instead of

&quot;^((?!(\\{.*\\})).)*$&quot;


use &quot;&quot;&quot; way:
&quot;&quot;&quot;^((?!(\{.*\})).)*$&quot;&quot;&quot;

Return values without val/var
You can simply write your containsTag without val and result of last line will return from function - it will return your's matches:
def containsTag(text: String): Boolean = {
  ! &quot;&quot;&quot;^((?!(\{.*\})).)*$&quot;&quot;&quot;.r.matches(text)
}

Choose your destiny
Sometimes it is good to use if/else but more readable match/case:
containsTag(text) match {
  case true =&gt;
    val randomPosition = Random.nextInt(allCharacters.length - 1)
    text.replace(&quot;}&quot;, allCharacters(randomPosition).toString)
  case _ =&gt;
    text
}

Feel the power
Collect it all together:
  val allCharacters = &quot;qwertyuiopasdfghjklzxcvbnmQWERTY&quot; +
    &quot;UIOPASDFGHJKLZXCVBNM1234567890!@#%*()_+-=[]{x}|?.;:`&quot;

  def removeTags(text: String): String = {

    def containsTag(text: String): Boolean = {
      ! &quot;&quot;&quot;^((?!(\{.*\})).)*$&quot;&quot;&quot;.r.matches(text)
    }

    containsTag(text) match {
      case true =&gt;
        val randomPosition = Random.nextInt(allCharacters.length - 1)
        text.replace(&quot;}&quot;, allCharacters(randomPosition).toString)
      case _ =&gt;
        text
    }
  }

  val res = removeTags(&quot;my friendly {name} is $userNickname$&quot;)

For random chars
According to answer in Replacing characters in a String in Scala you can use construction without while loop but with map + lambda like in code below:
  val allCharacters = &quot;+=-?&quot;

  def removeTags(text: String): String = {

    def containsTag(text: String): Boolean = {
      ! &quot;&quot;&quot;^((?!(\{.*\})).)*$&quot;&quot;&quot;.r.matches(text)
      true
    }

    containsTag(text) match {
      case true =&gt;
        text.map(
          (c) =&gt; c match {
            case '{' =&gt;
              val randomPosition = Random.nextInt(allCharacters.length - 1)
              allCharacters(randomPosition)
            case c =&gt; c // for another char which is not == {
          }
        )
      case _ =&gt;
        text
    }
  }

Then it will be good to divide functionality and create new Char =&gt; Char transformer function from anonymous (c) =&gt; c match {..} which we lay in text.map(...):
//...
def replaceRandom(c: Char): Char = {
  c match {
    case '{' =&gt;
      val randomPosition = Random.nextInt(allCharacters.length - 1)
      allCharacters(randomPosition)
    case c =&gt; c
  }
}

containsTag(text) match {
  case true =&gt; text.map((c) =&gt; {replaceRandom(c)})
  case _ =&gt; text
}
//...

Simplify code in two parts.
Part 1: simplify match/case in replaceRandom(c: Char): Char :
def replaceRandom(c: Char): Char = {
  c match {
    case '{' =&gt;
      val randomPosition = Random.nextInt(allCharacters.length - 1)
      allCharacters(randomPosition)
    case c =&gt; c
  }
}

// remove unneeded &quot;{&quot; and &quot;}&quot;
def replaceRandom2(c: Char): Char = c match {
  case '{' =&gt;
    val randomPosition = Random.nextInt(allCharacters.length - 1)
    allCharacters(randomPosition) 
  case c =&gt; c
}

Then part 2: simplify map((c) =&gt; {replaceRandom(c)}):
containsTag(text) match {
  case true =&gt; text.map((c) =&gt; {replaceRandom(c)})
  case _ =&gt; text
}

//remove unneeded &quot;{&quot; and &quot;}&quot;
containsTag(text) match {
  case true =&gt; text.map((c) =&gt; replaceRandom(c))
  case _ =&gt; text
}

//remove unneeded &quot;(c)&quot; to wildcard &quot;_&quot;
containsTag(text) match {
  case true =&gt; text.map(replaceRandom(_))
  case _ =&gt; text
}

//remove unneeded wildcard &quot;_&quot;
containsTag(text) match {
  case true =&gt; text.map(replaceRandom)
  case _ =&gt; text
}

Feel the power again
So for your task i will chose that variant: all functions are divided, logic placed is simple if/else and now good readable (as you see now it good readable also with if/else as i wrote to you above). After, when logic will grow you can use instead if/else match/case.
  def removeTags(text: String): String = {

    def containsTag(text: String): Boolean = {
      ! &quot;&quot;&quot;^((?!(\{.*\})).)*$&quot;&quot;&quot;.r.matches(text)
      true
    }

    def replaceRandom(c: Char): Char = c match {
      case '{' =&gt;
        val randomPosition = Random.nextInt(allCharacters.length - 1)
        allCharacters(randomPosition)
      case c =&gt; c
    }

    if (containsTag(text))
      text.map(replaceRandom)
    else text

  }

  val allCharacters = &quot;+=-?&quot;
  val e = &quot;my fr{{{iend{{{ly {{name} is $userNickname$&quot;
  println(removeTags(e))


P.S. or place to think

Maybe your regex not working? Check it at: Regex101
Why you need to replace } symbol with another random from allCharacters  which can be also }?
The bad, the good and the !: maybe try to rewrite your !noTagsRegex to noTagsRegex? It can be more readable for another people.


Also i want to see additions to my review too, that code is too verbose, so welcome!