Ceylon Modules Stop Classpath Drift With Named Versioned Descriptors
Ceylon modules make dependencies first-class with named, versioned descriptors and compile-time version range resolution, turning classpath drift into a build-time error.
23 Aug 2025, 02:25 UTC

Dependency drift is the quiet way large codebases break. Two libraries evolve independently, both pull in different versions of a transitive dependency, and the classpath ends up with conflicting classes. The build is reproducible on one machine and not another. Ceylon addresses this by making modules first-class: named, versioned units with explicit descriptors, resolved at compile time.
The useful takeaway is that a Ceylon module is not just a packaging convention. The module name, version, and imports are declared in source, and the compiler enforces compatibility before any code runs.
Module descriptors make dependencies explicit
In Ceylon 1.x each module has a module.ceylon file at the source root. It declares the module name, version, and the modules it requires. No hidden transitive classpath.
module com.example.math "1.0.0" {
shared package com.example.math;
}This file is author-controlled metadata. The name is a dot-separated identifier, the version follows semantic versioning conventions, and the shared packages expose the public API. Inspecting module.ceylon is the practical way to verify what a module publishes and what it depends on.
Version ranges are compile-time contracts
Consumers do not import a specific jar file. They import a module name with a version range.
import com.example.math "1.+";The range "1.+" means any 1.x version. The Ceylon compiler selects a compatible version from the module repositories available to the compilation. If no version satisfies the range, compilation fails. This moves version conflicts from runtime to compile time.
Ranges are intentionally narrow in production. A range like "1.2.*" pins minor changes, while "1.+" allows minor evolution. The decision is explicit in source, not in a build script.
Repository layout and multi-module workflow
Ceylon tooling expects a repository layout of name-version. A module com.example.math 1.0.0 is typically found under a repository path like com/example/math/1.0.0/. The compiler resolves imports by scanning configured repositories.
A minimal multi-module check can be performed locally with the Ceylon command line. Run ceylon compile in the consumer source directory with read access to the module source or repository. Required permissions are read access to source and the repository directory. Meaningful placeholders are the module name and version in module.ceylon and the import range in the consumer.
Expected checks are that the compiler reports the module as resolved and that no unsatisfied version range errors appear. Risks include publishing a module with a breaking change under a compatible range, which violates semantic versioning and breaks consumers.
Worked example
Producer module com.example.math 1.0.0 defines a shared package com.example.math with a public API. Consumer module com.example.app 0.1.0 declares import com.example.math "1.+"; in its module.ceylon. The compiler resolves a 1.x version of math at compile time. Changing the producer to 2.0.0 requires an explicit update of the import range in the consumer.
Verify resolution by inspecting the module descriptor after compilation and by reviewing compiler messages for unsatisfied version ranges. This confirms how ranges are interpreted without assuming runtime behavior.
Trade-off: discipline for isolation
Strict module boundaries and version ranges add design overhead. Every public type must live in a shared package, and breaking changes require a major version bump. The ecosystem is smaller than Java's Maven Central, so internal module hosting is common.
Maintenance status should be verified for 2026. Ceylon 1.x module semantics are well established, but tooling and security updates are not guaranteed. Java interoperability is version sensitive and should be tested locally.
Start with small internal modules, use semantic versions consistently, pin ranges in production, and verify resolution with ceylon compile before publishing. That gives compile-time isolation without over-engineering the module graph.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.