Ignoring some of these implementation details, the advantage of the second approach is the ease with which a `BazId` can be added.

Of course none of that matters unless Id's being different types actually means something. If you scatter around a lot of type testing code you're right back to adding `BazId` being difficult.

By which, I mean, these types need code that uses them to be useful. Be it polymorphic code or if type==bazId code. Without that the types are pointless. This is missing from the examples in the question. If such code appears in many places all those places must be changed to add a new type. 

That concern drives you back to the first approach. Some even do it with a big switch statement. Ideally you could simply add one new file and poof `BazId` works. The closest I've seen anything come to that is a class loader. But, well now you've simply moved the code that knows all the types to a configuration file. 

The fundamental problem is something must create an ordering for these types to give them their numbers. And that something can't be allowed to renumber old ones with new numbers or old strings become invalid. 

So what we're left with is each Id class must declare it's own number. And carefully avoid using any other Id class's number.

Do that and when adding new types you can avoid forcing rewrites of old code. Without that, either way ends up just as messy in the end.

This is not a new problem. It's the `serialVersionUID` part of [Java Serialization](https://www.baeldung.com/java-serialization) all over again.