This one is for my good friend Milen... Sometimes he frustrates me, but he always forces me to think...
Introduction
So if you've done any Liferay 7.x CE or DXP development, you may have encountered something similar to the following in your build.gradle:
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.6.0"
compileOnly group: "org.osgi", name: "osgi.cmpn", version: "6.0.0"
}
For the Maven users out there, I'm not going to include the pom equivalent, but I'm sure you can figure out how it would go.
One of the biggest mistakes that I see developers make is the selection of the version for modules they use. For example, I'm using portal-kernel version 2.6.0 above. This is really old; the Liferay Maven repo reports it was released in June, 2016.
And currently it looks like the latest available version is 2.62.0. You can see all of the available versions here.
So why do I pick such an old release? I certainly don't want to link to such an old version that likely has bugs or issues. Shouldn't I be picking the 2.62.0 version to get the latest?
Picking Old Versions
The Definitive Answer: No
Okay, so here's the skinny...
When you are building a module, you are not really linking to a jar. You are not including 2.6.0 or 2.62.0 or whatever into your module, you are only including meta information for the OSGi container that you need portal-kernel.
We as developers, we know that we always want to use the latest version available, when possible, so we get bugfixes, performance improvements, etc. So most of us want to grab 2.62.0 and use it as the version that we declare.
In OSGi, however, we never declare a specific version, we're always using a version range.
When I declare that my version is 2.6.0, the OSGi container thinks that I'm actually indicating that I want version [2.6.0,3.0.0). That's a version range from 2.6.0 up to (but not including) 3.0.0. This offers me a great bit of flexibility.
I can deploy my module to a Liferay 7.0 GA1 container or I could deploy it to the very latest Liferay 7.0 DXP Fix Pack 41. My version range means my module will work in all of these environments.
If instead I stuck with the 2.62.0 version, OSGi will treat that as [2.62.0,3.0.0). This version range is so narrow, it will not work in any of the 7.0 CE GAs and will actually only deploy to 7.0 DXP FP 40 or later.
So the version is not really the version you get, it is the minimum version you need to function. The OSGi container, it has some version of portal-kernel, you may or may not know what it is in your environment(s), the container will determine if the version it has is acceptable to your version range and may (or may not) start your module.
So, as a rule:
Wait, oldest you can get away with? What does that mean?
Oldest You Can Get Away With
For example, the com.liferay.portal.kernel.dao.search.SearchContainer class. I recently was copying a chunk of code from a certain Liferay DisplayContext class to emulate how it was handling the paged search container views.
I had my portal-kernel version set to 2.6.0 like I always do, and the code I copied went along something like this:
SearchContainer searchContainer = new SearchContainer(_renderRequest, getIteratorURL(), null, "there-are-no-rules");
if (isShowRulesAddButton()) {
searchContainer.setEmptyResultsMessageCssClass("there-are-no-rules");
}
Although I copied and only changes the strings, the IDE had the setEmptyResultsMessageCssClass() line marked as an error and it told me No Such Method errors. This made no sense because I literally lifted the code straight from the Liferay source, how could the method not exist.
Well, that comes back to the version range. Since I indicated 2.6.0, my code had to compile against 2.6.0 and this method doesn't exist in the SearchContainer interface from 2.6.0. I looked in Liferay's build.gradle file and found that they had used portal-kernel version 2.13.0. I changed my version to 2.13.0 and my compiler error went away.
So this tells me that, somewhere between portal-kernel version 2.6.0 and 2.13.0, this method was added to the SearchContainer interface. Since my code needs that method, the oldest version my code will work with is not 2.6.0, but more like 2.13.0.
Actually I guess it is possible it could have been added to the interface in 2.7.0, 2.8.0, etc, up to 2.13.0 so, if I were really a stickler about it, I could find the actual version where the method was introduced. I tend to value my time a lot, so instead I just settled on the version Liferay was also using.
Now, though, most of my modules use the version as 2.6.0 except for those that use this SearchContainer container code, this one uses version 2.13.0.
Why not just use 2.13.0 and keep everything the same?
Using Different Versions for Different Modules
Well, it comes down to flexibility. Most of my modules will work fine using the older version. This particular portlet uses a function in a SearchContainer that maybe I do or maybe I don't really need. If a client asks to run my portlet in their GA1 environment that might not have this version, well I can change the version in build.gradle, comment out the line using the etEmptyResultsMessageCssClass() method (and anything else that was not yet available) and then build and deploy my code.
In its current form it needed 2.13.0, but with a few modifications I could get a version that worked w/ 2.6.0.
Tripping
Or, how about this, this will really trip you out...
I create one bundle that has an Import-Package version range of [2.6.0,2.13.0) for com.liferay.portal.kernel.dao.search in one bundle that doesn't use setEmptyResultsMessageCssClass(), then on my second bundle I use an Import-Package version range [2.13.0,3.0.0) for the same package that does use the new method. Users can deploy both bundles into an environment, but OSGi will only enable the one that has the right version available.
It gets even better. You might deploy this to your GA1 envirronment where the first bundle starts. But then you upgrade to GA2 and, without changing anything from a deployment, the second bundle starts but the first does not. Completely transparent upgrades because my version ranges control which bundle will start and hence which code runs!
Conclusion
So I'm at the point where I hate the label "version". For a normal developer specifying a value in build.gradle, it means what version do I want to compile against. As we've seen, to the OSGi container it specifies the minimum version the module needs in order to start.
So even though it goes against your developer nature, avoid the latest version. Always use the oldest version your module needs.
Update
So I updated the blog post today after realizing that some of this might be a little jumbled, so just to clarify:
- You define a compile time version, a single version, in your pom.xml or build.gradle file; although you can use a range, it is not really the same thing as the version range discussed above.
- To specify a version range limitation in your bundle, you're going to use the Import-Package directive in your bnd.bnd file in a form like:
Import-Package:\ com.liferay.portal.kernel.dao.search;version="[2.6.0,2.13.0)",\ *
BND will stamp this range into your bundle's MANIFEST.MF file and OSGi will use this range when trying to satisfy dependencies in the bundle.
Take note of the wildcard '*' at the end of the Import-Package directive; you need that so BND will include all of the rest of your dependencies correctly.


