A better way to constrain case class construction in Scala

A lot of times in Scala, we want to constraint the creation of certain case classes. Unfortunately, as I found out recently, most of us (myself included) used to do it in a wrong way.

One of the most common ways of doing this is by declaring the case class as private and provide some factory method in the companion object to instantiate it.

private case class Positive(value: Int)

object Positive {
  def fromInt(num: Int): Option[Positive] =
    if (num >= 0) Some(Positive(num))
    else None
}


Although you might find this pattern in a lot of codebases, it has two problems: the companion object's apply and copy methods are still generated. So, the following are still allowed:

// apply from companion object
val wrong1 = Positive(-3)

// copy from companion object
val wrong2 = Positive.fromInt(2).get.copy(-3)


A trick that I found out recently was to use a sealed abstract case class to properly constraint the construction of my case classes.

So, the above code would look something like the following:

sealed abstract case class Positive(value: Int)

object Positive {
  def fromInt(num: Int): Option[Positive] =
    if (num >= 0) Some(new Positive(num) {})
    else None
}


The above syntax gives us the following benefits:

  • The default apply and copy methods in the companion object are not generated (because of abstract)
  • We cannot instantiate our case class, using new, outside the file (because of sealed)
  • We can still use pattern-matching as normal case classes

Maybe there are other better or more interesting ways of achieving the same results. If you do know of any, please let me know in the comments below.