Q:

Best practice for null-checking in Scala

I understand that null is frowned upon in Scala, and that one should always wrap optional values within an Option (or a "null type" if one is available).
The advantages are clear, there should never be a need to check against null values, and the resulting code is both safer and more pleasant to read.
In practice however, nothing prevents a value from being null - the rule is not enforced by the compiler and is more of a gentleman's agreement. This can easily break when interacting with Java APIs, for example.
Is there a best-practice for this? Say, for example, that I need to write an Url case class, and that instances of this class can be created from a java.net.URI:
object Url {
  def apply(uri: URI): Url = Url(uri.getScheme, uri.getHost, uri.getRawPath)
}

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Url = copy(protocol = value)
  def host(value: String): Url = copy(host = value)
  def path(value: String): Url = copy(path = value)
}

An URI instance can return null for getScheme, getHost and getRawPath. Is it considered sufficient to protect against these in the apply(URI) method (with require statements, for example)? Technically, in order to be entirely safe, it'd be better to protect the protocol, host and path helper methods, as well as the constructor, but that sounds like an awful lot of work and boilerplate.
Is it considered safe, instead, to protect against APIs known to accept / return null values (URI in our example) and to assume that external callers will either not pass null values or only have themselves to blame if they do?

A:

When dealing with pure functions, it is always better to stick to total implementations and instead of using null, require or exception throwing, to encode all failures in data. The types like Option and Either are there exactly for that. Both of them are monads, so you can use the "for"-syntax to with them.
The following modification to your code shows a total function apply, which only produces a meaningful value, when the input Java URI provides no nulls. We encode the notion of "meaningful" by wrapping the result in Option.
object Url {
  def apply(uri: java.net.URI): Option[Url] =
    for {
      protocol <- Option(uri.getScheme)
      host <- Option(uri.getHost)
      path <- Option(uri.getRawPath)
    }
    yield Url(protocol, host, path)
}

case class Url(protocol: String, host: String, path: String)

The function Option is a standard null-checking function, which maps null to None and wraps values in Some.

Concerning your "setting" functions, you can implement null-checking in them similarly, using Option. E.g.:
case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Option[Url] = 
    Option(value).map(v => copy(protocol = v))
  // so on...
}

However I would advice against that. In Scala it is conventional to never use nulls, so it is as well conventional to only implement null-handling for "bridge" APIs, when you get values from Java libs. That's why the null-checking does make perfect sense when converting from java.net.URI, but after that you are in Scala world, where there should be no nulls, and hence null-checking simply becomes redundant.

