Salesforce: Globals and Breaking Changes, Part 1

Salesforce has a number of rules regarding what changes can be made to global classes, interfaces, and methods once they have been released in a managed package, in order to prevent future package versions from breaking customers' code. Let's examine a couple scenarios where Salesforce falls short of this goal.

Here's a sampling of the rules surrounding globals that have been released in a managed package:

  • The method signature (return type, parameters) of a global method cannot be changed
  • The visibility of a global class or method cannot be reduced
  • Methods cannot be added to or removed from global interfaces
  • Abstract methods cannot be added to a global class
  • You cannot change a global class to no longer implement a global interface it previously implemented

Now, imagine we create a managed package containing the following class.

global class MyPackagedClass {
}

It doesn't do much, but since it does not have a constructor explicitly defined, an implicit default constructor will be provided, as in Java. Let's package this class up in version 1.0 of a managed package with a prefix of TEST, and install it into another Salesforce organization which I will call the subscriber org. In the subscriber org, we will create a class that consumes our packaged class.

public with sharing class MySubscriberClass {
    private TEST.MyPackagedClass c = new TEST.MyPackagedClass();
}

All set. Now, in the future we determine that we need to execute some logic whenever an instance of MyPackagedClass is created. So we add a constructor, and release version 1.1 of our package.

global class MyPackagedClass {
    global MyPackagedClass() {
        // do something here
    }
}

Since we are simply replacing the implicit global constructor with an explicitly defined global constructor, the code we wrote in the subscriber org should still work fine after we install the new version, right?

MySubscriberClass: line #, column #: Package Visibility: Constructor is not visible: [TEST.MyPackagedClass].<Constructor>()

Oh no, now it fails to compile! To fix it, we need to open MySubscriberClass, go to the Version Settings tab, and change it to reference version 1.1 of the TEST package. Why does this happen? My best guess is that Salesforce only looks at the explicitly defined constructor and sees that it was introduced in version 1.1 while our class was referencing 1.0. It does not realize that version 1.0 contained an implicit constructor, and so compilation fails until you change the class to referencing version 1.1.

Now, we've fixed the problem, but it would be better to never have broken our customers' code in the first place. So what options do we have?

1. Don't add constructors to global classes that were released with no explicit constructor

This is a given, but I'll list it anyway. If you just need to initialize some member variables, maybe you can do so inline with the variable declaration.

2. Don't make any classes global without a constructor

As long as you avoid the implicit global constructor, you're safe. You could define an empty global constructor and fill it in later, or start with a private / public constructor and introduce a global one afterwards, if needed.

3. Wait for Salesforce to fix it

I reported this issue to Salesforce and they acknowledged back in November of 2015. You can follow the known issue to receive status updates.

That's all for now. Stay tuned for part 2, where we'll examine an even trickier case.

Cheers,

Jeff

Previous Post Welcome!