Creating OSGi Modules and Sharing Packages

 

Overview

This guide walks through creating two OSGi modules in Liferay 7.4 and sharing a Java package between them using the standard API + Implementation pattern. This is the most common and correct approach for exposing reusable services in a Liferay-based architecture.

In OSGi, a bundle (module) does not expose its internal packages by default — everything is private unless explicitly exported. To share code between modules, the standard practice is to split functionality into:

· An API module — contains interfaces/DTOs and exports the package

· A Service (Implementation) module — implements the interface, imports the package, and registers the implementation as an OSGi component/service

The consuming module then simply imports the API package and looks up the service using @Reference injection.

Step 1: Create the API Module

Using Liferay Workspace + Blade CLI, generate a new API module:

blade create -t api -p com.sgc.greeting greeting-api

This generates a Gradle module under modules/greeting-api with the following structure:

greeting-api/
 └─ src/main/java/com/sgc/greeting/api/GreetingService.java

GreetingService.java

package com.sgc.greeting.api;
 
public interface GreetingService {
    String sayHello(String name);
}

Export the Package in bnd.bnd

Liferay/OSGi uses the bnd.bnd file (bnd tool) to control exports. Open greeting-api/bnd.bnd and add:

Bundle-Name: Greeting API
Bundle-SymbolicName: com.sgc.greeting.api
Bundle-Version: 1.0.0
Export-Package: com.sgc.greeting.api

The Export-Package line is what makes this package visible to other bundles — this is the core of “sharing” a package in OSGi.

Step 2: Create the Service / Implementation Module

blade create -t service-builder -p com.sgc.greeting greeting-service

Update your service.xml file

<?xml version="1.0"?>

<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.4.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_7_4_0.dtd">

 

<service-builder package-path="com.sgc.greeting">

<author>Arman</author>

<namespace>GREETING</namespace>

<entity name="Greeting" local-service="true" remote-service="false">

<!-- PK fields -->

<column name="greetingId" type="long" primary="true" />

<!-- Group instance -->

<column name="groupId" type="long" />

<column name="companyId" type="long" />

<!-- Audit fields -->

<column name="userId" type="long" />

<column name="userName" type="String" />

<column name="createDate" type="Date" />

<column name="modifiedDate" type="Date" />

<!-- Other fields -->

<column name="name" type="String" />

<column name="message" type="String" />

<order by="asc">

<order-column name="name" />

</order>

<finder name="Name" return-type="Collection">

<finder-column name="name" />

</finder>

</entity>

</service-builder>

blade gw :modules:greeting-service:greeting-service-service:buildService

Add a Dependency on the API Module

In greeting-service/build.gradle:

dependencies {
    compileOnly project(":modules:greeting-api")
    compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "default"
    compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}

Implement the Interface as an OSGi Component

package com.sgc.greeting.service.impl;
 
import com.sgc.greeting.api.GreetingService;
import org.osgi.service.component.annotations.Component;
 
@Component(
    immediate = true,
    service = GreetingService.class
)
public class GreetingServiceImpl implements GreetingService {
 
    @Override
    public String sayHello(String name) {
        return "Hello, " + name + "! Welcome to Liferay 7.4 OSGi modules.";
    }
}

service = GreetingService.class registers this implementation in the OSGi Service Registry under that interface — this is how Liferay's Declarative Services (DS) model publishes the service.

Step 3: Consume the Shared Package in a Third Module

Create a consumer module, for example an MVC portlet:

blade create -t mvc-portlet -p com.sgc.greeting.web greeting-web

Add the API Dependency in build.gradle

dependencies {
    compileOnly project(":modules:greeting-api")
}

Inject the Shared Service via @Reference

package com.sgc.greeting.web.portlet;
 
import com.sgc.greeting.api.GreetingService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import javax.portlet.*;
import java.io.IOException;
 
@Component(
    immediate = true,
    property = {
        "javax.portlet.name=com_sgc_greeting_web",
        "javax.portlet.display-name=Greeting Portlet"
    },
    service = Portlet.class
)
public class GreetingPortlet extends MVCPortlet {
 
    @Reference
    private GreetingService greetingService;
 
    @Override
    public void doView(RenderRequest request, RenderResponse response)
        throws IOException, PortletException {
 
        request.setAttribute("greetingMessage", greetingService.sayHello("Arman"));
        super.doView(request, response);
    }
}

@Reference tells the OSGi Declarative Services runtime to inject whatever component is registered for GreetingService — this is the actual “sharing” moment, resolved dynamically at runtime.

Step 4: Deploy in the Correct Order

Order matters because of dependency resolution:

blade gw :modules:greeting-api:deploy


blade gw :modules:greeting-service:greeting-service-api:deploy

 blade gw :modules:greeting-service:greeting-service-service:deploy
blade gw :modules:greeting-web:deploy

Or deploy all modules at once from the workspace root:

blade gw deploy

Step 5: Verify in Gogo Shell

Connect to Liferay's OSGi console:

telnet localhost 11311

Check bundle states:

lb greeting

You should see all three bundles as Active. Then verify the exported package is visible:

package com.sgc.greeting.api

Comments