Constructors and global classes in Apex

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:

[code language="java"]
global class InstantiateMeIfYouCan {
    global void foo() {
        System.assert(false, 'OK, so you instantiated me. Now what?');
    }
}
[/code]

Salesforce automatically generates an implicit, global constructor with no parameters. When releasing this class in a package, it can be instantiated and used from the customer’s org:

[code language="java"]
public class ISwearICanInstantiateYou {
    public static void foo() {
        new VSTest.InstantiateMeIfYouCan().foo();
    }
} [/code]

(… where VSTest is the package’s namespace).

So far, so good.

Updating our global class with a new constructor

It turns out that after releasing our class, now for some reason we want it to be instantiable only with a parameter specified in the constructor. So we add a new constructor:

[code language="java"]
global class InstantiateMeIfYouCan {
    global InstantiateMeIfYouCan(Boolean coolModeActivated) {

    }

    global void foo() {
        System.assert(false, 'OK, so you instantiated me. Now what?');
    }
}
[/code]

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:

Dependent class is invalid and needs recompilation: ISwearICanInstantiateYou: line 3, column 16: Constructor not defined: [VSTest.InstantiateMeIfYouCan]()

OK, so now we know this is a breaking change. Let’s try to fix this and release a new version.

Recovering the no-param constructor

We have already released our 1-param constructor, and it’s global, so there’s no way to return to the original status. Now the only thing we can do, is adding an explicit, no-param constructor:

[code language="java"]
global class InstantiateMeIfYouCan {
    global InstantiateMeIfYouCan() {

    }

    global InstantiateMeIfYouCan(Boolean coolModeActivated) {

    }

    global void foo() {
        System.assert(false, 'OK, so you instantiated me. Now what?');
    }
}
[/code]

Again, the system allows us to package the new version and release it. And again, the customer can install the new version with no issues. But when running their code…

Dependent class is invalid and needs recompilation: ISwearICanInstantiateYou: line 3, column 16: Package Visibility: Constructor is not visible: [VSTest.InstantiateMeIfYouCan].<Constructor>()

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:

InstantiateMeIfYouCan

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:

[code language="java"]
public class SoYouThoughtICouldNotInstantiateYou {
    public static void foo(){
        new VSTest.InstantiateMeIfYouCan().foo();
    }
} [/code]

It works directly because it is taking the latest version by default.

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.

What if we create an explicit, private no-param constructor?

[code language="java"]
global class InstantiateMeIfYouCan {
    private InstantiateMeIfYouCan() {}

    global void foo() {
        System.assert(false, 'OK, so you instantiated me. Now what?');
    }
}
[/code]

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 the 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:

[code language="java"]
global class InstantiateMeIfYouCan {
    global InstantiateMeIfYouCan() {
        throw new InvalidInstantiationException();
    }

    global InstantiateMeIfYouCan(Boolean coolModeActivated) {
        ...
    }

    global void foo() {
        System.assert(false, 'OK, so you instantiated me. Now what?');
    }
}
[/code]

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:

[code language="java"]
public class ISwearICanInstantiateYou {
    public static void foo() {
        VSTest.InstantiateMeIfYouCan instance;

        try {    // Firstly, try to instantiate with default values
            instance = new VSTest.InstantiateMeIfYouCan();
        }
        catch (VSTest.InvalidInstantiationException ex) {
            // Not available: use the constructor with an explicit argument
            instance = new VSTest.InstantiateMeIfYouCan(false);
        }

        instance.foo();
    }
} [/code]

So now, there’s no need at all for the customer to create a new version of their code or product, because it will be prepared to work with and without the no-param constructor in advance.

2 thoughts on “Constructors and global classes in Apex

  1. So what happens if the code changes between versions where the signature does not? It sounds like the client code is linking at least against the signatures of the old version. It would be odd if it got the old version’s code though. It would seem that your solution relies on the client code taking the latest code to work, otherwise old client code will still get the empty constructor.

    I’m afraid this really does sound like broken behaviour.

    • Exactly, client code is linked against the signatures of the old version, but it takes the latest code, so as long as the signatures do not change, it is possible to upgrade our package in customer’s org and get their code to work with our latest version without them needing to update their own code.

      At least, that’s the current behaviour.

Leave a Reply