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

Maven for Repeatable Builds

Posted on August 23, 2009 By Luis Fernandez

You can ship fast or you can ship twice. The teams that ship once usually have repeatable builds.
If your jar changes from laptop to laptop, you are not unlucky, you just need Maven to keep your house in order.

Maven is not a silver bullet, but for repeatable builds it is the closest thing we have right now, and it beats the pile of Ant scripts that drift with every commit. The promise is simple and strong: same source plus same configuration gives the same artifact on any box, be it your workstation, the Hudson server in the corner, or that old build slave under someone’s desk. To get there you fix versions, tame plugins, and stop the random fetch from the internet during a release. When Maven resolves a dependency without a version, it can surprise you tomorrow. When a plugin has no version, it can surprise you tonight. If your build touches a remote snapshot on every run, you are rolling dice, not building software.

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>repeatable-sample</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <jdk.version>1.6</jdk.version>
    <junit.version>4.7</junit.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.0.2</version>
          <configuration>
            <source>${jdk.version}</source>
            <target>${jdk.version}</target>
            <encoding>${project.build.sourceEncoding}</encoding>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.4.3</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-release-plugin</artifactId>
          <version>2.0</version>
          <configuration>
            <tagBase>https://svn.example.com/repos/project/tags</tagBase>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-enforcer-plugin</artifactId>
          <version>1.0-alpha-4</version>
          <executions>
            <execution>
              <id>enforce-java</id>
              <goals><goal>enforce</goal></goals>
              <configuration>
                <rules>
                  <requireJavaVersion>
                    <version>${jdk.version}</version>
                  </requireJavaVersion>
                  <requireEncoding>
                    <encoding>${project.build.sourceEncoding}</encoding>
                  </requireEncoding>
                </rules>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

The recipe above pins versions for dependencies and plugins, sets the same Java level and encoding everywhere, and removes guesswork. Put a version on every plugin through pluginManagement, not just in the build plugins list, so children inherit a fixed set. Keep your dependency versions in dependencyManagement and keep the concrete list clean in the modules that do not need to know the numbers. Add enforcer to block surprises like a teammate compiling with a different JDK. For sanity in releases use the maven release plugin to cut tags and switch from snapshot to release and back without human error.

# Cut a release with no interactive prompts in CI
mvn -B -V release:clean release:prepare release:perform

# Daily build that fails fast on tests
mvn -B -V -e -Dmaven.test.failure.ignore=false clean verify

# Lock current versions to your POMs so no floating updates sneak in
mvn -B versions:use-dep-version -Dincludes=groupId:artifactId -DdepVersion=1.2.3
mvn -B versions:use-latest-releases # or decide not to, and pin everything

Repeatability also needs a home for artifacts. Pointing every build at the public repo and hoping ibiblio never goes down is a nice dream. Set up Nexus or Artifactory as a single internal source, make it proxy central, and mirror everything through it. That gives you a cache, a place to publish your own snapshots and releases, and a stable history when upstream slips or disappears. Your Maven settings.xml should mirror central to your repo manager, turn off random updates for releases, and keep snapshots where they belong. Limit version ranges and skip system path tricks, those are fast ways to get non repeatable binaries.

<settings>
  <mirrors>
    <mirror>
      <id>internal</id>
      <name>Company Mirror</name>
      <url>http://nexus.local:8081/repository/public</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>

  <profiles>
    <profile>
      <id>strict-updates</id>
      <repositories>
        <repository>
          <id>central-through-nexus</id>
          <url>http://nexus.local:8081/repository/public</url>
          <releases>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
          </releases>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>

  <activeProfiles>
    <activeProfile>strict-updates</activeProfile>
  </activeProfiles>
</settings>

Once you have that mirror in place you can turn your CI jobs from hopeful to boring, which is exactly what you want. A Hudson job that runs on a schedule or on every Subversion change should call Maven with batch flags and a fixed JDK, and it should fail the build if tests fail. If your job needs to compile with a different profile, make it explicit and never rely on a default that might drift. Publishing artifacts to your repo manager from CI closes the loop so your teammates pull the exact bits that passed tests, and your next stage picks them up for staging or production without a manual copy step that can go wrong.

# Hudson shell build step
export JAVA_HOME=/opt/jdk6
export MAVEN_OPTS="-Xms256m -Xmx512m -XX:MaxPermSize=128m"
export MAVEN_SKIP_RC=true

mvn -B -V -e -Dmaven.repo.local=/data/maven-repo \
    -DskipTests=false clean verify

# Publish artifacts on success
mvn -B -V deploy

There are some gotchas worth calling out so your build does not bite someone at 2 a.m. Do not use version ranges in dependencies unless you enjoy surprises. Keep SNAPSHOT use limited to pre release work and never cut a release while pointing to a snapshot of a parent or a dependency, the release plugin will try to help but you still want discipline. Avoid systemPath dependencies because they bind your build to a specific folder and blow up on the next machine. If you must use a native library or a vendor jar, deploy it to your repo manager with a clear group and version so it behaves like the rest. And write down the JDK and Maven versions used by the project in a readme, even better, enforce them with enforcer so the rule is not a suggestion.

All this can sound like overhead when you are chasing a feature, but the first time a build differs between your box and the server you will wish you had pinned everything. Ant can do great things and it is still all over the place, but wiring your own dependency system and plugin versions is a second job and you will miss a case. With Maven the defaults push you toward a clean layout, standard life cycle, and matching outputs across machines. Add a repo manager like Nexus or Artifactory, a friendly Hudson job, and a short checklist for releases, and your team can focus on code instead of jar hunting and version gremlins. Shipping gets a lot less dramatic when the bits do not change under your feet between commit and release.

Repeatable builds are not magic, they are just choices you make early so the future you is not chasing ghosts at midnight.

Productivity & Workflow Software Engineering

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