Implementing new Custom Field APIs in a backward-compatible manner

With Jira 8.10, we’ve added several APIs that can be used to improve the indexing performance of custom fields. Since then, we’ve received feedback that because of the way the APIs are defined (they introduce new classes), it’s impossible to maintain a single app binary that implements the APIs and works with versions older than 8.10.

While this does not break our API guarantees, it certainly hurts adoption of the new APIs. For this reason, in Jira 8.12, we’ve released a newer version of the APIs that will let the vendors create a single binary without impacting Jira version compatibility. We’re already using the same approach in Advanced Roadmaps (formerly Portfolio).

If your app is only targeting Jira 8.12+, you can stop reading here and use the new APIs just like you usually would. However, if you want to maintain compatibility with older Jira releases, here’s an outline of what needs to be done:

  1. When implementing the APIs, instead of using the NonNullCustomFieldProvider
    and CustomFieldPrefetchedData classes that came with 8.10/8.11, use these same classes from com.atlassian.jira.issue.customfields.vdi package. It’s just a trivial name change.
  2. Within the plugin, provide your own minimal copies of these classes. They do not have to work, they are only needed to make the code compile and run on older versions. Creating CustomFieldPrefetchedData.java and NonNullCustomFieldProvider.java with the following class bodies should be enough to satisfy the Java VM when using older Jira versions.
package com.atlassian.jira.issue.customfields.vdi;
// required for compatibility with Jira 8.11 and older
public class CustomFieldPrefetchedData { }
package com.atlassian.jira.issue.customfields.vdi;
// required for compatibility with Jira 8.11 and older
public interface NonNullCustomFieldProvider { }
  1. In the OSGi imports section of your pom.xml, import the API package as optional, e.g. like that:
<Import-Package>
    com.atlassian.jira.issue.customfields.vdi;resolution:=optional,
    *;resolution:=optional
</Import-Package>

The result of these changes will be as follows:

  • on versions prior to 8.12, the classes from your plugin will be used to satisfy the linking process. The APIs will not be used, but everything else will work. The plugin subsystem will not complain about the missing classes, because the import is marked as optional.
  • On versions 8.12+, the OSGi declaration will take precedence over the bundle classloader. The classes will be imported from the Jira API package and the new indexing APIs will be used.

In case of any questions, please comment below.

1 Like

Hi @pbruski,

There is something I don’t understand, the implementation for me is not backward-compatible.

The entry point for the new API is to implement the public NonNullCustomFieldProvider CustomFieldType.getNonNullCustomFieldProvider() method in our custom field type class.

This means our Custom Field type has a class reference to a non-existent NonNullCustomFieldProvider class (this exists only in Jira 8.12+). As soon as the Custom Field is loaded, crashes will occur.

Adding the pom.xml optional imports didn’t fix it for me. Here is where I placed it:

<project>
  <build>
    <plugins>
      <plugin>
        <instructions>
          <Import-Package> ... </Import-Package>
        </instructions>
      </plugin>
    </plugins>
  </build>
</project>

I get this exception in the console (among others) as soon as I try to open an issue with the custom field in:

Caused by: java.lang.ClassNotFoundException: com.atlassian.jira.issue.customfields.vdi.NonNullCustomFieldProvider not found by ... [307]
        at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1639)
        at org.apache.felix.framework.BundleWiringImpl.access$200(BundleWiringImpl.java:80)
        at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:2053)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 461 more

Thanks!

I think you missed the “Within the plugin, provide your own minimal copies of these classes.” part.

Hi @pbruski,

I am not familiar with this kind of class loading gimmick, so what I now understand is we create a fake empty class in the Atlassian package and it will only be used at runtime in older versions. In 8.12+ it will load the actual Jira API classes.

package com.atlassian.jira.issue.customfields.vdi;

public interface NonNullCustomFieldProvider { }

public class CustomFieldPrefetchedData { }

This seems to work, the API is being called in 8.12, and older versions does not crash. Thank you for your time.

Regards,

1 Like

same here. Can you please give more clear instructions on how to achieve this please. For e.g. UPM Data Center Licensing there is a backwards compatibility dependency for the pom.xml that includes such classes. Could you not provide the same thing for these classes too?

I mean this thing

        <dependency>
            <groupId>com.atlassian.upm</groupId>
            <artifactId>data-center-licensing-compatibility</artifactId>
            <version>1.0</version>
            <scope>compile</scope>
        </dependency>

And in the imports we do

<Import-Package>
!com.atlassian.upm.datacentercompatibility.*,
</Import-Package>

I would prefer the same approach :slight_smile:

1 Like

This… These dummy classes make navigating through the source, and debugging, harder, and is confusing for anyone viewing the plugin source.

If you have done compatibility libraries before for this same reason, why not now?

2 Likes

That’s spot on, glad you were able to make it work. I will modify the original post to include the example of what needs to be done.

1 Like

@clouless I get that a library with these two empty classes would be easier to use, but the best I can offer now is including more specific examples in the post (I will add your idea to the backlog though).

@jechlin

This… These dummy classes make navigating through the source, and debugging, harder, and is confusing for anyone viewing the plugin source.

It’s not the same. Even if (or once) we provide that library, it would contain these exact empty/dummy classes. There’s a fundamental difference between this approach and what UPM is doing. As far as I understand, the UPM case is an API, our case is an SPI. So while UPM can provide a full reimplementation of an API on older versions, all we can do is to provide dummy classes, because the required indexing logic using that SPI is in Jira.

1 Like