Dual-Backend CPAN Modules (Option B)
April 9, 2026 · View on GitHub
Overview
This document describes the plan to support CPAN modules that ship a .java file
alongside the traditional .xs, allowing the same distribution to work on both
standard Perl (perl) and PerlOnJava (jperl).
Related documentation: Module Porting Guide — update the "Status: Not yet implemented" note there when each phase is completed.
Motivation
Currently, Java XS modules must be bundled inside the PerlOnJava JAR (Option A). This limits Java XS authorship to the PerlOnJava project itself. Option B enables any CPAN module author to ship a Java backend without depending on PerlOnJava releases.
See also: GitHub Discussion #25
Architecture
CPAN Distribution Layout
A dual-backend module ships three implementations in the same tarball:
Foo-Bar-1.00/
├── lib/
│ └── Foo/
│ ├── Bar.pm # Main module — calls XSLoader::load()
│ └── Bar/
│ └── PP.pm # Pure Perl fallback (optional but recommended)
├── java/
│ └── Foo/
│ └── Bar.java # Java XS implementation for PerlOnJava
│ └── META-INF/
│ └── perlonjava.properties # Manifest for jcpan
├── Bar.xs # C XS implementation for standard Perl
├── Makefile.PL
├── t/
│ └── basic.t
└── META.json
The java/ directory uses Perl module paths (not Java package paths) for
familiarity with Perl authors.
Install-Time Compilation
When jcpan install Foo::Bar encounters a java/ directory:
- Copy
.pmfiles to~/.perlonjava/lib/(existing behavior) - Read
java/META-INF/perlonjava.propertiesfor module metadata - Compile
.javaagainstperlonjava.jar:javac -cp perlonjava.jar -d /tmp/build java/Foo/Bar.java jar cf ~/.perlonjava/auto/Foo/Bar/Bar.jar -C /tmp/build . - Copy source to
~/.perlonjava/auto/Foo/Bar/Bar.java(for recompilation)
Install Layout
~/.perlonjava/
├── lib/ # .pm files
│ └── Foo/
│ ├── Bar.pm
│ └── Bar/
│ └── PP.pm
└── auto/ # compiled Java XS
└── Foo/
└── Bar/
├── Bar.jar # compiled module JAR
└── Bar.java # source (kept for recompilation)
This mirrors Perl's auto/Module/Name/Name.so convention.
XSLoader Search Order
When XSLoader::load('Foo::Bar') is called:
- Built-in registry — Java classes in the PerlOnJava JAR
(
org.perlonjava.runtime.perlmodule.*) auto/JARs —~/.perlonjava/auto/Foo/Bar/Bar.jar- Fail — die with
"Can't load loadable object for module Foo::Bar"(triggers PP fallback if the module uses the standard eval/require pattern)
Manifest Format
# java/META-INF/perlonjava.properties
perl-module=Foo::Bar
main-class=org.perlonjava.cpan.foo.Bar
perl-module— the Perl package name (used forauto/path calculation)main-class— the fully-qualified Java class name (used for dynamic loading)
Implementation Plan
Phase 1: XSLoader auto/ JAR Discovery
Goal: Teach XSLoader.java to find and load JARs from ~/.perlonjava/auto/.
Changes:
src/main/java/org/perlonjava/runtime/perlmodule/XSLoader.java- After the built-in registry lookup fails, check for
~/.perlonjava/auto/<module-path>/<leaf>.jar - Use
DynamicClassLoader.loadJar()to add the JAR to the classpath - Read
META-INF/perlonjava.propertiesfrom the JAR to find the main class - Call the static
initialize()method on the main class
- After the built-in registry lookup fails, check for
Test:
# Manually place a pre-compiled JAR and verify XSLoader finds it
mkdir -p ~/.perlonjava/auto/Test/JavaXS/
cp TestJavaXS.jar ~/.perlonjava/auto/Test/JavaXS/
./jperl -e 'use Test::JavaXS; print Test::JavaXS::hello(), "\n"'
Phase 2: jcpan Java Compilation Support
Goal: Teach jcpan / ExtUtils::MakeMaker.pm to detect and compile java/ directories.
Changes:
src/main/perl/lib/ExtUtils/MakeMaker.pm- In
_handle_xs_module()(or a new_handle_java_xs()), detectjava/directory - Read
java/META-INF/perlonjava.properties - Invoke
javacto compile the.javafile againstperlonjava.jar - Package into a JAR and install to
~/.perlonjava/auto/ - Copy source
.javafile alongside the JAR
- In
Dependencies:
- Requires a JDK (not just JRE) on the user's machine
perlonjava.jarpath must be discoverable (e.g., from$0or an env var)
Test:
# Create a minimal dual-backend distribution and install it
jcpan install /tmp/Test-JavaXS-1.00/
./jperl -e 'use Test::JavaXS; print Test::JavaXS::hello(), "\n"'
Phase 3: Recompilation on JDK Upgrade
Goal: Detect stale JARs and recompile from saved source.
Changes:
- Store the Java version used for compilation in
~/.perlonjava/auto/Foo/Bar/Bar.jar.meta - On load failure (e.g.,
UnsupportedClassVersionError), attempt recompilation from the saved.javasource
This phase is optional and can be deferred.
Phase 4: Documentation and Ecosystem
Goal: Make it easy for CPAN authors to add Java XS support.
Deliverables:
- Example dual-backend distribution on GitHub
- Template
java/META-INF/perlonjava.properties - Blog post / announcement
- Update
docs/guides/module-porting.md— remove the "Not yet implemented" warning from Option B
Open Questions
-
Java package naming for CPAN modules — Should we enforce
org.perlonjava.cpan.<module>or allow any package? The manifest makes arbitrary packages possible. -
Multiple Java files — Some modules may need multiple
.javafiles. Shouldjcpancompile all.javafiles in thejava/tree? -
Java dependency JARs — If a Java XS module depends on third-party JARs (e.g., a JDBC driver), how should those be specified and installed? Possible:
java/lib/*.jardirectory, or ajava/dependencies.txtmanifest. -
CLASSPATHfor project-local modules — For users who want to load their own Java classes without going through CPAN, theJava::System::load_classAPI (proposed in Discussion #25) is a separate but complementary feature.
Progress Tracking
Current Status: Not started
Phases
- Phase 1: XSLoader
auto/JAR discovery - Phase 2: jcpan Java compilation support
- Phase 3: Recompilation on JDK upgrade (optional)
- Phase 4: Documentation and ecosystem
Reminders
- When Phase 1 is complete, update
docs/guides/module-porting.mdto note thatauto/JAR loading is functional - When Phase 4 is complete, remove the "Not yet implemented" warning from
docs/guides/module-porting.md
Action Items
-
Create a GitHub issue to track implementation of Dual-Backend CPAN Module support. The issue should include:
- Summary of the feature: allow CPAN modules to ship a
java/directory with Java XS implementations thatjcpancompiles at install time - The 4-phase implementation plan (XSLoader discovery, jcpan compilation, recompilation, documentation)
- Links to:
- This design doc:
dev/design/DUAL_BACKEND_CPAN_MODULES.md - Module porting guide:
docs/guides/module-porting.md(Option B section) - Discussion #25: https://github.com/fglock/PerlOnJava/discussions/25
- This design doc:
- Open questions from this document
- Label:
enhancement
- Summary of the feature: allow CPAN modules to ship a
-
Reply to Discussion #25 with the following:
- A GitHub issue has been opened to implement support for dual-backend CPAN modules (link to the issue)
- The module porting guide now documents a proposed "Publish a Dual-Backend CPAN Module" workflow (not yet implemented): https://github.com/fglock/PerlOnJava/blob/master/docs/guides/module-porting.md
- A detailed implementation plan has been created: https://github.com/fglock/PerlOnJava/blob/master/dev/design/DUAL_BACKEND_CPAN_MODULES.md
- Invite feedback on the proposed
java/directory convention andauto/install layout
Related Documents
- Module Porting Guide — user-facing documentation
- XS Fallback Mechanism — how XSLoader fallback works
- XSLoader Architecture — XSLoader internals
- CPAN Client Support — jcpan implementation
- GitHub Discussion #25 — original feature request