implicit constructor

It is well known that special care must be taken when creating global classes in Apex, because global classes, methods and member variables that are released in a package, cannot be removed in further versions of the same package. Some of the restrictions are well explained in official Salesforce docs like this one and this one, but there are some issues for which it is difficult to be prepared until we face the problem or test it ourselves. This is the case of the thrilling world of constructors in global classes.

How do constructors behave in global classes?

Pretty much the same as in non-global ones. But there are some important nuances to be considered.

Let’s say… we are creating a class with no “particular” way of instantiation. We don’t need to pass any argument, there’s no needed initialisation, and it can be freely instantiated. We can then omit the constructor:

As with any other class, global or not, Apex automatically removes the implicit, no-param constructor. This common behaviour is detailed in the official documentation:

If you write a constructor that takes arguments, you can then use that constructor to create an object using those arguments. If you create a constructor that takes arguments, and you still want to use a no-argument constructor, you must include one in your code. Once you create a constructor for a class, you no longer have access to the default, no-argument public constructor. You must create your own.

And Salesforce allows us to package this new version. One could think that, implicit or not, the previously existing constructor with no parameters was global and had already been packaged, so the system would forbid us to perform this action. But the truth is, it doesn’t. When we release this new version, it is possible for the customer to upgrade the package in their org, despite of the fact that the new version breaks their existing code. When trying to run their code with the new version, an error similar to this arises:

Weird, isn’t it? So we have explicitly added the constructor and set it as global. We know it is global. But the platform does not get it correctly, even though when entering the class in the customer’s org, it informs about the availability of those methods:

Actually, not so weird. The existing class ISwearICanInstantiateYou is linked to a previous version of our package. It’s a matter of changing the version it is using by editing the class and going to Version Settings. If we create another, new class in the customer’s org that makes use of the mysterious no-param constructor:

So, the fact that we didn’t explicitly declare the no-param constructor in the first version, caused issues in the end (our updates are throwing compile errors in customer’s org). This makes it highly advisable to always declare an explicit, global no-param constructor in our global classes in order to prevent potential issues. But it is not always possible: sometimes it does not make sense to allow customers to instantiate our class.

This way we are “booking” the constructor and at the same time, preventing customers from using it unless we decide to make it global in a future version. This would work as long as we keep the restriction of not being able to instantiate de class with the default constructor from the outside.Otherwise, after releasing the first version with the private constructor, an update that changes it into a global one would obviously need an update in existing customer code to start leveraging it. We could think of an alternative solution that would work without them needing to update nothing but our package.

Implementing an explicit prohibition

Let’s create an explicit, global no-param constructor that throws a special exception:

By including the global constructor in the first version, we make sure it will be available in future releases if we need it. On the other hand, we are explicitly forbidding its usage by throwing an exception that we have created for this situation. And if we need to “unlock” the constructor and make it available in a new release, we can always change its implementation with no issues. Actually, the customer would need to update their code anyway in order to use the new constructor, unless they implemented something like this to anticipate this situation: