OSGi to Maven Extension Migration
OSGi to Maven Extension Migration
This recipe guides maintainers (and AI-assisted sessions) through converting a legacy OSGi Lucee extension to the Maven-based extension model in Lucee 7.
For build file snippets, version matrices, CI configuration, and pitfall details, see the technical specification at /docs/technical-specs/maven-extension-migration.yaml.
The worked example is the Image extension (3.0.x OSGi → 3.1.x Maven). For concepts, see Maven Based Extensions.
When to Migrate
Migrate when the extension targets Lucee 7+, you want standard Maven dependency management, and you are ready to drop OSGi bundle wiring.
Keep an OSGi line when you must support Lucee 6.2 for BIF/tag extensions, or need a stable bugfix branch for older installs.
A common strategy: bump the major/minor version line for the Maven branch (e.g. Image 3.0.x for 6.2, 3.1.x for 7+).
Minimum Lucee Core Version
The lucee-core-version in MANIFEST.MF must reflect what your extension actually uses — not a blanket 7.1 for every migration.
| Extension type | Typical minimum | Example |
|---|---|---|
| Manifest handlers only (resource, cache, JDBC) | 7.0.0.68+ | S3 declares 7.0.0.211-BETA |
BIF functions (FLD with maven=) |
7.0.2.86+ | Most function-only extensions |
Custom tags (TLD with maven=) |
7.1.0.2+ | Rare — Image defines <cfimage> |
Image requires 7.1.0.2+ not because Maven extensions in general need 7.1, but because it ships a TLD with custom tags. S3-style extensions (manifest-only, no FLD/TLD) follow the 7.0 baseline.
What Changes
At a high level:
- Build — Maven compiles the JAR; Ant assembles the
.lex(see Maven Based Extensions) - Manifest —
start-bundles: false; keep the extension UUID unchanged - FLD/TLD — replace
bundle-name/bundle-versionwithmaven="{maven}";build.xmlsubstitutes coordinates at package time - Dependencies — move from
libs/and manual copies intosource/java/pom.xml - Artifacts — publish a full
.lex(with embeddedmaven/repo) and optionally a lite extension (.lite.lex, Maven classifierlite)
Migration Checklist
1. Build system
- [ ] Add root
pom.xmlandsource/java/pom.xml - [ ] Wire
build.xmlviamaven-antrun-plugin - [ ] Ensure
maven-builddepends oninit(wipestarget/each build) - [ ] Copy dependencies with Maven repository layout; run
copy-parent-poms.sh - [ ] Package full
.lexand lite extension.lite.lex
2. Extension metadata
- [ ] Set
start-bundles: false - [ ] Set
lucee-core-versionper table above - [ ] Keep extension
id(UUID) unchanged
3. FLD / TLD (if applicable)
- [ ] Switch to
maven="{maven}"on all<class>and<tag-class>entries - [ ] Add
javax-tag-classif dual javax/jakarta tag support is needed
4. Dependencies
- [ ] Move JARs into
pom.xml;providedscope fororg.lucee:lucee - [ ] Remove
systemscope, manualbuild.xmlJAR copies, and legacyorg.luceerepackages
5. Java source
- [ ] Remove OSGi bundle manifest from extension JAR
- [ ] Replace OSGi-specific version lookups
6. CI and testing
- [ ]
mvn clean install -Dgoal=install - [ ] Test with
lucee/script-runner,extensionDir: target/ - [ ] Upload
target/*.lexas artifacts
Details for each step: maven-extension-migration.yaml.
Full vs Lite Extension
Most Maven extensions publish two install packages:
| Package | File | Contents |
|---|---|---|
| Full | {name}-extension-{version}.lex |
Metadata + embedded maven/** JARs |
| Lite extension | {name}-extension-{version}.lite.lex |
Metadata only (no maven/**) |
The lite extension is for Lucee Light/Zero and Docker setups where dependencies resolve from Maven Central at install time. The full .lex is for offline/self-contained installs and is what CI typically tests against.
Maven coordinates for the lite extension use classifier lite:
org.lucee:image-extension:3.1.0.9-BETA:lex # full
org.lucee:image-extension:3.1.0.9-BETA:lite:lex # lite extension
See Extension Installation for install methods.
Testing
mvn -B -e -f pom.xml clean install -Dgoal=install
ls -lh target/*.lex
Run extension tests via script-runner with extensionDir pointing at target/. Label test components (e.g. labels="image") and pass testAdditional for extension-local tests.
If tests use _internalRequest for .cfm fixtures, paths must use contractPath() — not raw filesystem paths. See the tech spec test_patterns.internal_request section.
Common Issues
| Problem | Quick fix |
|---|---|
Old JARs still in .lex after dependency removal |
maven-build must depend on init; delete target/ locally |
| Lite extension fails at runtime offline | Use full .lex, or pre-populate {lucee-server}/../mvn/ |
| Tags fail on 7.0.x | Extension defines TLD tags — requires 7.1.0.2+ |
MissingIncludeException in CI tests |
Use contractPath() web paths in _internalRequest |
Full pitfall reference: tech spec pitfalls section.
Reference Repositories
| Extension | Use as template for |
|---|---|
| extension-s3 | Manifest-only handler, lite extension, 7.0 baseline |
| extension-mail | Simple Maven migration with TLD |
| extension-image | FLD + TLD (custom tags), dependency cleanup, 7.1 minimum |
| extension-dynamodb | Cache handler, no FLD/TLD |
| extension-ftp | Resource provider |
AI Session Quick Start
- Read Maven Based Extensions and
maven-extension-migration.yaml - Pick the closest reference repo (S3 for manifest-only; Image for FLD+TLD)
- Determine
lucee-core-version: 7.0 default; 7.1.0.2+ only if the extension defines TLD custom tags - Follow the checklist above; verify
target/*.lexafter build - Add a lite extension if transitive dependencies are large
- Keep the OSGi branch until 6.2 support is officially dropped