Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Project Jigsaw and Modularity

Posted on January 28, 2017 By Luis Fernandez

Project Jigsaw is finally knocking on the door and everyone who writes Java is about to learn a new word: modules.

For years the classpath has been our bag of tricks and pain. You dump jars in there and pray conflicts do not explode at runtime. It works until it does not. You know the drill. Two libs share a package name, some private class leaks out, someone sets accessible on a field and the whole house shakes. Jigsaw aims at a cleaner story with a module system baked into the platform. The pitch is simple. Strong boundaries. Clear dependencies. Better encapsulation. The JDK itself gets split into named pieces so you can ship what you need. This also brings a tidy syntax that reads well. The idea is not new for Java devs who played with OSGi or similar setups, but now it comes straight in the language toolbox and in the Java runtime. That matters for readability and long term maintenance. You declare what your code needs and what it shows to the world. The compiler and the runtime can check it. No more guessing what a jar contains or which friends are allowed behind the curtain.

// module-info.java
module com.example.blog {
    requires java.sql;
    exports com.example.blog.api;
}

That tiny file is the headline of your module. The module name is a stable identifier, not a package. Inside, requires lists dependencies and exports lists the packages you agree to make public. Everything else stays inside the fence. There is more than the bare minimum. Qualified exports let you export a package only to friend modules to keep internals semi private. Transitive requires says that your consumers also read your dependency. There is even requires static for optional stuff that is only needed at compile time. The syntax reads like plain English and that is a win. If you ever wrote a build file and wished the compiler could see the intent, this feels nice. The mental model is short. The module declares what it reads and what it tells the world. No trick. No magic scanning. Less side effects to carry in the head.

// module-info.java with extras
module com.example.app {
    requires transitive java.xml;
    requires static com.fasterxml.jackson.core;
    exports com.example.app.api;
    exports com.example.app.internal to com.example.tools;
}

Services get a fresh coat of paint too. The old ServiceLoader trick is still there and now it ties into modules with uses and provides ... with. That means a module can say it consumes a service and another can say it implements it. The runtime wires this up from the module path. Reflection gets a new gate as well. Exports are about compile time and public APIs. Opens are about deep reflection for things like frameworks that poke fields and methods. You can open a whole module or a single package. You can also open to a given module if you want to be strict with who gets the keys. This is how we keep strong encapsulation while still letting popular frameworks do their work. The core JDK becomes a set of modules such as java.base and java.sql and code cannot just climb over fences with a set accessible blast. It needs permission.

// module-info.java with services and opens
module com.example.payment {
    requires java.logging;
    uses com.example.payment.spi.Gateway;
    provides com.example.payment.spi.Gateway with com.example.payment.stripe.StripeGateway;
    opens com.example.payment.model to com.fasterxml.jackson.databind;
}

Now the part everybody asks about. Migration. You can run code on the module path or on the trusty classpath. Unnamed modules live on the classpath and they can still see the world. Named modules live on the module path and follow the rules. If you place a plain jar on the module path it becomes an automatic module. The name is inferred from the jar file name unless the jar declares Automatic-Module-Name in its manifest. That attribute gives you a stable name without going full module right away. It is a simple step for library authors and a big help for apps that want clean graphs. Watch out for split packages. Two modules cannot define the same package. That is a common snag when you juggle old jars. Tools can lend a hand. jdeps tells you who depends on what and can suggest the closest JDK modules. The new jlink tool builds a trimmed runtime image for your app so you can ship only what you need. This feels like a nice upgrade for microservices and small command line tools where a tiny footprint matters.

# analyze dependencies for a jar
$ jdeps --module-path mods --multi-release 9 --generate-module-info tmp out/app.jar

# create a custom runtime with only needed modules
$ jlink --module-path $JAVA_HOME/jmods:mods \
       --add-modules com.example.app \
       --output build/runtime \
       --launcher runapp=com.example.app/com.example.app.Main

Tooling is catching up. Early access builds of the JDK ship with the module system, and IDEs are starting to draw the module graph and compile module-info.java properly. Maven can compile when you set the release flag and recent plugins support mixed code where tests still live on the classpath. Gradle has samples out in the wild and the community is pushing guides as we speak. A safe first step is to add Automatic-Module-Name to libraries so consumers get stable names even before going all in. For apps, pick one small module first, keep package names unique, and put more weight on readable names than clever tricks. Built in checks will reward that discipline. If you live in OSGi land, you can keep using it. You can even make a module that carries OSGi metadata. Different goals, same code. The common thread is better boundaries and code that declares intent. The more we lean into clear syntax, the less time we spend chasing classpath ghosts and the easier it gets to reason about our builds.

Here is a tiny starting point that fits in a gist and shows the flow from code to run. Nothing fancy, just the core pieces most apps need to test the waters with strong encapsulation and simple service wiring.

// src/com.example.greet/module-info.java
module com.example.greet {
    exports com.example.greet.api;
}

// src/com.example.greet/com/example/greet/api/Greeting.java
package com.example.greet.api;

public interface Greeting {
    String say(String name);
}

// src/com.example.greet.impl/module-info.java
module com.example.greet.impl {
    requires com.example.greet;
    provides com.example.greet.api.Greeting
        with com.example.greet.impl.HelloGreeting;
    exports com.example.greet.impl; // only if you want to expose the impl
}

// src/com.example.greet.impl/com/example/greet/impl/HelloGreeting.java
package com.example.greet.impl;

import com.example.greet.api.Greeting;

public class HelloGreeting implements Greeting {
    public String say(String name) {
        return "Hello " + name;
    }
}

// src/com.example.app/module-info.java
module com.example.app {
    requires com.example.greet;
    requires com.example.greet.impl;
    uses com.example.greet.api.Greeting;
}

// src/com.example.app/com/example/app/Main.java
package com.example.app;

import com.example.greet.api.Greeting;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        Greeting g = ServiceLoader.load(Greeting.class)
                                  .findFirst()
                                  .orElseThrow(() -> new IllegalStateException("No Greeting"));
        System.out.println(g.say(args.length > 0 ? args[0] : "World"));
    }
}

Build the three modules, place them on the module path, and run the app with --module com.example.app/com.example.app.Main. The service loader finds the provider because the provider module declared it. Encapsulation keeps internals tidy. The syntax reads well enough that new devs can find their way in minutes. Once you are happy, use jdeps to see what each piece reads, then play with jlink to make a compact image. Repeat in small steps. Keep names boring and clear. Watch split packages. Add the manifest name in jars you ship out. You will feel the classpath stress fade. The sweet bonus is that runtime errors turn into compile time checks. That is the kind of trade many teams like to make.

Project Jigsaw and Modularity is not just a feature list, it is a nudge toward code that says what it means. It trims the JDK, gives us better defaults, and puts a name on boundaries we always knew we needed. The language of modules is short and readable, and the tools give good signals when you cross a line. The story is still being refined in mailing lists and early builds, so there will be tweaks, but the core feels solid. If your app has been living on a giant classpath, start a branch and try a tiny module. If you publish libraries, add that manifest name and clean split packages. If you like small deployable runtimes, get friendly with jlink. This feels like a change we can embrace without drama because it rewards the habits we want anyway. Clear APIs. Small surfaces. Fewer foot guns. Stronger contracts.

Try one module this week and write down what breaks and what reads better.

Then keep the parts that made your code simpler and ship with confidence.

Code Development Practices Software Engineering coding-practicesjavaspring

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes