Never, never, never use String in Java – 7 years later

Some years ago I've written about never to use String in Java.

"Never, never, never use (unwrapped) String or long or int. Why? Those primitive types have no semantic meaning. They are hard to understand, hard to maintain, and hard to extend."

Now it is time to revisit this post and see what I've learned since then.

The idea is that instead of String for data like firstnames, use a FirstName type instead. This leads to more readable code, as the semantics of data is clearer. Together with more readable APIs this leads to less bugs. For example this Java code

public void bookTicket(
  String name, 
  String firstName, 
  String film, 
  int count, 
  String cinema);

is hard to use, as it doesn't explain what constraints there are on the data to use the method correctly. Can firstName be empty? One way for a better API is to use Monads like Option in Scala or Maybe in Haskell to make clear which values are optional. In Scala the fact that firstName can be empty (optional) could be expressed as

def bookTicket(
  cinema:String) = { ... }

What other constraints does name and firstName have? To make the API more readable my article those years ago suggested to introduce classes for the parameters. The constructor of these class then makes constraints clear and guards constraints. Our example then becomes

public void bookTicket(
  Name name, 
  FirstName firstName, 
  Film film, 
  Count count, 
  Cinema cinema);

Today a lot of my code uses unboxed tagged types in Scala to enforce constraints on data and make code easier to understand.

One way to write tagged unboxed types is

   val name: String @@ Name

where Name is the tag. In Scala this is infix notation for a generic type @@ with the type parameters String and Name. The infix notation is the same as @@[String,Name]. This type can easily be declared as

type Tagged[U] = { type Tag = U }
type @@[T, U] = T with Tagged[U]


trait Name

def name(n:String): String @@ Name = n.asInstanceOf[String @@ Name]

With unboxed tagged types in Scala the example code can be rewritten as:

def bookTicket(
  name:String @@ Name, 
  firstName:String @@ FirstName, 
  film:String @@ Film, 
  count:Int @@ Count, 
  cinema:String @@ Cinema) 

It has the same benefits as the code with those special classes above, e.g. is type safe to use, prevents bugs introduced by using the wrong order of call arguments and ensures constraints on the parameters. The name parameter of type String @@ Name could have the constraints of not being empty, having a minimum number of characters (bad idea when going international), starts with a capital character or does not contain digits or HTML code.

This way not only has less overhead due to the fact that the types are unboxed, but also has the benefit of reusable code, e.g. Count can be used with other base types too. Count could have different constraints for Int or String:

def count(i:Int): Int @@ Count = {
  if (i>=0) i.asInstanceOf[Int @@ Count]
  else throw ...

def count(s:String): String @@ Count = { ... }

The largest benefit I see, is how the API is easier for me as a developer to use. I see that name is basically a String with some constraints and a tag. In the case of String the developer seeing the code wonders what an name really is. With some conventions he knows there is a Name() method somewhere to check constraints and get a String @@ Name out of a String.

Overall it's best to combine all approaches like in:

def firtstName(f:String): Option[String @@ FirstName] = {
  if (f == null || f.trim.isEmpty) None
  else if (....) throw new IllegalArgumentException(...)
  else Some(f.trim)

often combined with a FirstName.isValid method to prevent exceptions.

This leads to

def bookTicket(
  name:      String @@ Name, 
  firstName: Option[String @@ FirstName], 
  film:      Film, 
  count:     Int @@ Count, 
  cinema:    Cinema) 

and reaps the benefits of a clearer API that is less bug prone to use.

Stephan Schmidt Administrator
CTO Coach , svese
Stephan is a CTO coach. He has been a coder since the early 80s, has founded several startups and worked in small and large companies as CTO. After he sold his latest startup he took up CTO coaching. He can be found on LinkedIn or follow him in Twitter.
follow me