Classloading in Liferay

Libraries (or JAR-files if you will) exist in many different locations in your Liferay installation. Some reside in Tomcat's lib folder, others are included in the lib folder of Liferay's ROOT. And of course, there are the libraries that are included in your own portlets. It's no secret that Liferay performs some magic tricks to enable access to different classes in different contexts. This complexity is mostly abstracted away from developers, so you don't have to worry if class X is indeed available for you from within a portlet. However, there are some very specific particularities that you need to be aware of as a Liferay developer. Welcome to the wonderful world of classloading in Liferay!

By the way, understanding the principles in this article might help you in certifying for Liferay, as you're guaranteed to get some questions on this topic in your certification exam!

Classpaths in Liferay

Roughly speaking, 3 different classpaths are active in a Liferay installation.

The global classpath includes all JAR files that are defined in Tomcat's lib and lib/ext folders. All deployed webapps (including ROOT and your own portlets and hooks) have access to these libraries. Examples of included libraries are servlet-api.jar (Servlet API), portlet.jar (Portlet 2.0 API) and portal-service (Liferay Service API). Because they are already available, you don't want to include these libraries in your own portlet or hook. Otherwise, the library will be loaded twice, which in most cases will result in deploy failure.

The portal classpath includes all JAR files from the lib folder of ROOT. These 200+ libraries are only accessible from within the ROOT application itself. Examples of included libraries are portal-impl.jar (Liferay Implementation), util-java.jar, util-bridges.jar, util-taglib.jar, Spring libraries, ...  

The portlet/hook classpath includes all JAR files from the lib folder of the deployed portlet or hook. These libraries are only accessible from within the portlet or hook itself.

Who can access what?

With all those different classpaths, it's important to know which libraries are available in which context.

Inside Liferay's ROOT, classes and JSPs can only access the global and the portal classpath.

Inside a portlet, you have access to both the global and the portlet classpath. This means that both the global libraries and the portlet libraries are loaded together. Or why it is extremely important never to include libraries in your portlet that are already available in the global classpath! If you're using Maven and you still want to compile against those libraries in the global classpath, put the scope of your dependency to provided.

Inside a hook the same rules apply as for portlets. Most hooks have access to both the global and the hook classpath. There is however one exception: JSP hooks. At deploy time, JSPs that are overriden in a JSP hook replace the original JSP in the ROOT. At this point, the JSP can only access the global and portal classpaths, NOT the hook classpath. For instance, it is impossible to let an overridden JSP access a class that's contained in a third-party JAR that's included in the lib folder of the hook.

Sharing libraries between portlets/hooks

If you've been paying attention, you'll agree with me that the only way to share libraries between different portlets is to add those libraries tot the global classpath, i.e. in Tomcat's lib or lib/ext folder. However, generally, this is not a great idea. At least, you should make sure that those libraries don't end up to be duplicated in different classpaths. An example.

Suppose all of your portlets make heavy use of the Spring framework. To not include libraries like spring-context.jar, spring-beans.jar, spring-core.jar in every portlet, you decide to add them to the global classpath in tomcat/lib/ext. But, oh my, suddenly your portal stops working. Why? Because Liferay's ROOT already includes these Spring libraries, and now Liferay tries to load those libraries twice for the ROOT application. Result: conflicts!

If you still wish to share libraries through the global classpath, take the following thoughts into account:

  • Make sure the libraries don't already exist on the portal classpath. If so, forget about sharing these libraries and include them in every portlet or hook that needs them.
  • Make sure the libraries are not included in any of the deployed portlets or hooks. If so, set the scope of these dependencies to provided instead of compile, so they are not packaged in the WAR file.
  • Prepare to lose some amount of flexibility. Indeed, all your portlets and hooks will now have to commit to the same version of the library you're adding. This can be good for consistency, but do you really want to recompile all existing portlets and hooks if you suddenly need to use the latest version of library X in one of them?

Dependency JARs

Liferay does provide a mechanism to gain access to classes in the portal classpath while inside your portlet by the means of portal dependency JARs. This is a property you can set in the liferay-plugin-package.properties configuration file. It accepts a list of JAR files that are to be found in the portal classpath.

WEB-INF/portlet-plugin-package.properties
name=sample-portlet
module-group-id=liferay
module-incremental-version=1
...
portal.dependency.jars=commons-logging.jar,commons-io.jar

At deploy time, Liferay will copy over all matching JARs from the portal's lib folder to the lib folder of the portlet or hook. So you don't actually get access to the portal classpath. Instead, the necessary JAR files are copied over to the portlet/hook classpath.

In some rare cases, it can be useful to define these dependency JARs. Some framework JARs are enhanced by Liferay to enable some specific behavior. And by including the JAR this way, you're sure you have the exact same implementation version of the library. But in general, try to avoid this type of configuration as it tends to create more problems than it solves. If you're using it anyway and your compilation units depend on those JAR's, don't forget to specify these dependencies as provided in Maven. Otherwise, they will be added twice to the portlet's lib folder.

About portal-impl.jar

Being able to include dependency JARs from the portal context into your own portlet or hook effectively allows you to depend on portal-impl.jar in your plugin. Don't. Do. This. The portal-impl.jar library is not meant to be exposed to custom plugins. That's the whole point of having an API (portal-service.jar). And remember what we said about portal-service.jar: you don't explicitely add this library to your portlet as it's already available in the global classloader.

Evaluation time!

If you can solve these questions, you've totally mastered the classloading concepts in Liferay. Feel free to post a comment if you have more questions on the topic.

  1. I created a JSP hook that uses Apache POI to export the list of user groups to Excel through a new button in the UI. Which of the following approaches will work if you know that poi.jar is included in ROOT/WEB-INF/lib?
    1. Add poi.jar to the lib folder of my JSP hook
    2. Add poi.jar to the list of portal.dependency.jars in the portlet-plugin-package.properties file of my JSP hook
    3. Add poi.jar to the lib/ext folder of Tomcat
    4. None of the above
  2. I created a service hook that uses POI to export the list of user groups to Excel each time a new user group is created. Which of the following approaches will work? Multiple answers are possible
    1. Add poi.jar to the lib folder of my hook
    2. Add poi.jar to the list of portal.dependency.jars in the portlet-plugin-package.properties file of my hook
    3. Add poi.jar to the lib/ext folder of Tomcat
    4. None of the above
  3. My portlets always use the Vaadin framework. Which of the following approaches will work if you know that vaadin.jar is included in ROOT/WEB-INF/lib? Multiple answers are possible.
    1. Add vaadin.jar to every portlet's lib folder separately
    2. Add vaadin.jar to the lib/ext folder of Tomcat
    3. Add vaadin.jar to the lib/ext folder of Tomcat and remove vaadin.jar in ROOT/WEB-INF/lib
    4. Add vaadin.jar to the list of portal.depency.jars in the portlet-plugin-package.properties file of my portlets

Visit http://blogs.aca-it.be for more blogs on Liferay and Java.

Blogs
Great article Peter !!
Can you confirm answers for the questions:
1 -- d : because jsp hook are runnig in portal classloader.
2 -- a, b : because service hook is running in hook's classloader.
3 -- a, c, d
Great Article with nice coverage of Liferay Classloading principles !!!
Nice write up.. Got a clear understanding of Liferay's classpath
What versions of Liferay is this true? Are the jars loaded from the temp directory if running in tomcat? We've also had issues where one portlet had an older version of a dependency and this was loaded in the classpath for a different portlet which had a newer version of the dependency instead of the newer version. This really caused a problem. Our dependencies are managed by maven and the jars include the version in the names.
@Tom
- The principles are not bound to a certain Liferay version. However, between different application servers there might be small variations. E.g. Jboss packages a lot more JAR files in the "global" classpath than Tomcat.
- The JAR files are never loaded from temp. Maven imposes a problem indeed if you upgraded a dependency but didn't do a clean deploy. A clean deploy means, deleting the portlet/hook from the webapps folder of Tomcat before redeploying the WAR file. In our Liferay team, this is the standard procedure for deploying portlets and hooks.
Wonderful article Peter, a really "basic" article.

If I can afford a hint, in question number 2 I would repeat that poi.jar is included in ROOT/WEB-INF/lib.
HI Peter,
Thanks for your nice article, but i'm confused when i share services among multiple portlets, in this case portlets seems can access other portlet's implementation class. This conflicts with your article that war can't see each other in liferay.

Example:
I have a Plugin common-service.war, in this plugin, i use liferay service builder to generate the service layer, and separate it into common-service.jar and common-service-impl.jar (those *impl.class)

and i have another plugin myPortlet.war, i defines required-deployment-contexts=common-service and include common-service.jar in the myPortlet.war's library. This way, myPortlet can invoke the services provided by common-service.war. Please note that common-service-impl.jar is NOT included in the myPortlet.war.

As two war files can't see each other during runtime, why myPortlet.war can invoke the service from common-service.war, the implementation class common-service-impl.jar is not available to myPortlet.war?
I guess liferay uses some special class loading for this scenarios?

Peter, any advice on this?
Hi,

Excellent question! Indeed, the servlet specification doesn't allow WAR files to see each other's classes. Therefore the classloader of portlet X would normally not be accessible for portlet Y.

However, Liferay circumvents this by introducing classloader proxies (CLPs). So I guess you're right that Liferay performs some magic to make this work. For your reference, this link describes the principle more in depth: http://agile-reflections.opnworks.com/2012/04/sharing-classes-between-liferay-plugins.html
@Peter ........... I need a bit of info. I created a DefaultLandingPageAction.java through a hook plugin inside a package com.hook.test.path. I just took the sourcecode of this file and introduced two variables there so that they can be used in setting landing page path in portal-ext.properties. I also added these two properties::
override.default.landing.page.path=true;
login.events.post=com.liferay.portal.events.ChannelLoginPostAction,com.hook.test.path.DefaultLandingPageAction,com.liferay.portal.events.LoginPostAction.

As usual I had the classloading problem, since my java file isnt in portal-impl.jar. I just want to know what else I have to do? And what is the use of overiding the path variable mentioned here:: https://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Customizing%20the%20default%20page%20after%20login.
@Arunjyoti,

I'm not sure what you're trying to accomplish in your custom landing page action, but in 99% of the cases you can work around a dependency on classes from portal-impl.jar. Could you post (or PM) the code for your DefaultLandingPageAction, so I can take a look and see where internal references could be replaced by service-level references.
@Peter ........... I solved it. I had wrongly made my class under Struts Action. And with the code I modified, I can land into the group page, a user is assigned.

Now I am working on which group page a user wants to land if he is assigned to multiple groups, based upon his choice.
For the first question answer->d
Can anyone explain what will happen if put the jar file in both location tomcat/lib/ext and root/web-inf/lib.I am new to liferay.It will be much helpful for me if i understand this...Please Response quick
[...] Olá pessoal, tudo bem? eu estou com vários portlets que utilizam o hibernate, e ate então as lib's do hibernate estão dentro de cada portlet. Todos os portlets funcionam muito bem. O problema é o... [...] Read More
[...] There is no shortcut. Don’t rely on any sample questions online(including this post) completely. Best way to prepare is to practice tutorial here . Even the experienced developers should try to go... [...] Read More