Search
Calendar
June 2025
S M T W T F S
« May    
1234567
891011121314
15161718192021
22232425262728
2930  
Archives

Posts Tagged ‘Maven’

PostHeaderIcon 🚀 Mastering Flyway Migrations with Maven

Managing database migrations can be tricky, but Flyway simplifies the process with versioned scripts for schema changes, data updates, and rollbacks.
Here’s a quick guide with useful examples:

✅ 1️⃣ Creating a Table (V1 – Initial migration)
“`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50)…,
email VARCHAR(100)…,
created_at TIMESTAMP…
);
“`
✅ 2️⃣ Inserting Sample Data (V2)
“`
INSERT INTO users (username, email) VALUES
(‘alice’, ‘alice@example.com‘),
(‘bob’, ‘bob@…’);
“`

✅ 3️⃣ Adding a New Column (V3 – Schema change)
“`
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
“`

✅ 4️⃣ Renaming a Column (V4)
“`
ALTER TABLE users RENAME COLUMN email TO contact;
“`

♻ Undo Script (U4__Rename_email_to_contact.sql)
“`
ALTER TABLE users RENAME COLUMN contact TO email;
“`

✅ 5️⃣ Deleting a Column (V5)
“`
ALTER TABLE users DROP COLUMN last_login;
“`

♻ Undo Script (U5__Revert_remove_last_login.sql)
“`
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
“`

✅ 6️⃣ Deleting Specific Data (V6)
“`
DELETE FROM users WHERE username = ‘alice';
“`

♻ Undo Script (U6__Revert_delete_user.sql)
“`
INSERT INTO users (username, contact) VALUES (‘alice’, ‘alice@example.com‘);
“`

💡 Configuring Flyway in pom.xml
To integrate Flyway into your Spring Boot or Java project, add the following configuration in your `pom.xml`:
“`
<properties>
<flyway.version>11.4.1</flyway.version>
</properties>

<dependencies>
<!– Flyway Core –>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>

<!– Database Driver (Example: PostgreSQL) –>
<dependency>
“org.postgresql:postgresql:runtime”
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>myuser</user>
<password>mypassword</password>
<schemas>public</schemas>
<locations>filesystem:src/main/resources/db/migration</locations>
</configuration>
</plugin>
</plugins>
</build>
“`

📂 Migration scripts should be placed in:
`src/main/resources/db/migration/`
Example:
“`
V1__Create_users_table.sql
V2__Insert_sample_data.sql
V3__Add_last_login_column.sql
“`

💡 Flyway Maven Plugin Commands
👉Apply migrations:
“`mvn flyway:migrate“`
👉Undo the last migration (if `flyway.licenseKey` is provided):
“`mvn flyway:undo“`
👉Check migration status:
“`mvn flyway:info“`
👉Repair migration history:
“`mvn flyway:repair“`
Clean database (⚠Deletes all tables!):
“`mvn flyway:clean“`

PostHeaderIcon Understanding Dependency Management and Resolution: A Look at Java, Python, and Node.js

Understanding Dependency Management and Resolution: A Look at Java, Python, and Node.js

Mastering how dependencies are handled can define your project’s success or failure. Let’s explore the nuances across today’s major development ecosystems.

Introduction

Every modern application relies heavily on external libraries. These libraries accelerate development, improve security, and enable integration with third-party services. However, unmanaged dependencies can lead to catastrophic issues — from version conflicts to severe security vulnerabilities. That’s why understanding dependency management and resolution is absolutely essential, particularly across different programming ecosystems.

What is Dependency Management?

Dependency management involves declaring external components your project needs, installing them properly, ensuring their correct versions, and resolving conflicts when multiple components depend on different versions of the same library. It also includes updating libraries responsibly and securely over time. In short, good dependency management prevents issues like broken builds, “dependency hell”, or serious security holes.

Java: Maven and Gradle

In the Java ecosystem, dependency management is an integrated and structured part of the build lifecycle, using tools like Maven and Gradle.

Maven and Dependency Scopes

Maven uses a declarative pom.xml file to list dependencies. A particularly important notion in Maven is the dependency scope.

Scopes control where and how dependencies are used. Examples include:

  • compile (default): Needed at both compile time and runtime.
  • provided: Needed for compile, but provided at runtime by the environment (e.g., Servlet API in a container).
  • runtime: Needed only at runtime, not at compile time.
  • test: Used exclusively for testing (JUnit, Mockito, etc.).
  • system: Provided by the system explicitly (deprecated practice).

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>
    

This nuanced control allows Java developers to avoid bloating production artifacts with unnecessary libraries, and to fine-tune build behaviors. This is a major feature missing from simpler systems like pip or npm.

Gradle

Gradle, offering both Groovy and Kotlin DSLs, also supports scopes through configurations like implementation, runtimeOnly, testImplementation, which have similar meanings to Maven scopes but are even more flexible.


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
    

Python: pip and Poetry

Python dependency management is simpler, but also less structured compared to Java. With pip, there is no formal concept of scopes.

pip

Developers typically separate main dependencies and development dependencies manually using different files:

  • requirements.txt – Main project dependencies.
  • requirements-dev.txt – Development and test dependencies (pytest, tox, etc.).

This manual split is prone to human error and lacks the rigorous environment control that Maven or Gradle enforce.

Poetry

Poetry improves the situation by introducing a structured division:


[tool.poetry.dependencies]
requests = "^2.31"

[tool.poetry.dev-dependencies]
pytest = "^7.1"
    

Poetry brings concepts closer to Maven scopes, but they are still less fine-grained (no runtime/compile distinction, for instance).

Node.js: npm and Yarn

JavaScript dependency managers like npm and yarn allow a simple distinction between regular and development dependencies.

npm

Dependencies are declared in package.json under different sections:

  • dependencies – Needed in production.
  • devDependencies – Needed only for development (e.g., testing libraries, linters).

{
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "mocha": "^10.2.0"
  }
}
    

While convenient, npm’s dependency management lacks Maven’s level of strictness around dependency resolution, often leading to version mismatches or “node_modules bloat.”

Key Differences Between Ecosystems

When switching between Java, Python, and Node.js environments, developers must be aware of the following fundamental differences:

1. Formality of Scopes

Java’s Maven/Gradle ecosystem defines scopes formally at the dependency level. Python (pip) and JavaScript (npm) ecosystems use looser, file- or section-based categorization.

2. Handling of Transitive Dependencies

Maven and Gradle resolve and include transitive dependencies automatically with sophisticated conflict resolution strategies (e.g., nearest version wins). pip historically had weak transitive dependency handling, leading to issues unless careful pinning is done. npm introduced better nested module flattening with npm v7+ but conflicts still occur in complex trees.

3. Lockfiles

npm/yarn and Python Poetry use lockfiles (package-lock.json, yarn.lock, poetry.lock) to ensure consistent dependency installations across machines. Maven and Gradle historically did not need lockfiles because they strictly followed declared versions and scopes. However, Gradle introduced lockfile support with dependency locking in newer versions.

4. Dependency Updating Strategy

Java developers often manually manage dependency versions inside pom.xml or use dependencyManagement blocks for centralized control. pip requires updating requirements.txt or regenerating them via pip freeze. npm/yarn allows semver rules (“^”, “~”) but auto-updating can lead to subtle breakages if not careful.

Best Practices Across All Languages

  • Pin exact versions wherever possible to avoid surprise updates.
  • Use lockfiles and commit them to version control (Git).
  • Separate production and development/test dependencies explicitly.
  • Use dependency scanners (e.g., OWASP Dependency-Check, Snyk, npm audit) regularly to detect vulnerabilities.
  • Prefer stable, maintained libraries with good community support and recent commits.

Conclusion

Dependency management, while often overlooked early in projects, becomes critical as applications scale. Maven and Gradle offer the most fine-grained controls via dependency scopes and conflict resolution. Python and JavaScript ecosystems are evolving rapidly, but require developers to be much more careful manually. Understanding these differences, and applying best practices accordingly, will ensure smoother builds, faster delivery, and safer production systems.

Interested in deeper dives into dependency vulnerability scanning, SBOM generation, or automatic dependency update pipelines? Subscribe to our blog for more in-depth content!

PostHeaderIcon [Devoxx FR 2024] Mastering Reproducible Builds with Apache Maven: Insights from Hervé Boutemy

Introduction

In a recent presentation, Hervé Boutemy, a veteran Maven maintainer, Apache Software Foundation member, and Solution Architect at Sonatype, delivered a compelling talk on reproducible builds with Apache Maven. With over 20 years of experience in Java, CI/CD, DevOps, and software supply chain security, Hervé shared his five-year journey to make Maven builds reproducible, a critical practice for achieving the highest level of trust in software, as defined by SLSA Level 4. This post dives into the key concepts, practical steps, and surprising benefits of reproducible builds, based on Hervé’s insights and hands-on demonstrations.

What Are Reproducible Builds?

Reproducible builds ensure that compiling the same source code, with the same environment and build tools, produces identical binaries, byte-for-byte. This practice verifies that the distributed binary matches the source code, eliminating risks like malicious tampering or unintended changes. Hervé highlighted the infamous XZ incident, where discrepancies between source tarballs and Git repositories went unnoticed—reproducible builds could have caught this by ensuring the binary matched the expected source.

Originally pioneered by Linux distributions like Debian in 2013, reproducible builds have gained traction in the Java ecosystem. Hervé’s work has led to over 2,000 verified reproducible releases from 500+ open-source projects on Maven Central, with stats growing weekly.

Why Reproducible Builds Matter

Reproducible builds are primarily about security. They allow anyone to rebuild a project and confirm that the binary hasn’t been compromised (e.g., no backdoors or “foireux” additions, as Hervé humorously put it). But Hervé’s five-year experience revealed additional benefits:

  • Build Validation: Ensure patches or modifications don’t introduce unintended changes. A “build successful” message doesn’t guarantee the binary is correct—reproducible builds do.
  • Data Leak Prevention: Hervé found sensitive data (e.g., usernames, machine names, even a PGP passphrase!) embedded in Maven Central artifacts, exposing personal or organizational details.
  • Enterprise Trust: When outsourcing development, reproducible builds verify that a vendor’s binary matches the provided source, saving time and reducing risk.
  • Build Efficiency: Reproducible builds enable caching optimizations, improving build performance.

These benefits extend beyond security, making reproducible builds a powerful tool for developers, enterprises, and open-source communities.

Implementing Reproducible Builds with Maven

Hervé outlined a practical workflow to achieve reproducible builds, demonstrated through his open-source project, reproducible-central, which includes scripts and rebuild recipes for 3,500+ compilations across 627+ projects. Here’s how to make your Maven builds reproducible:

Step 1: Rebuild and Verify

Start by rebuilding a project from its source (e.g., a Git repository tag) and comparing the output binary to a reference (e.g., Maven Central or an internal repository). Hervé’s rebuild.sh script automates this:

  • Specify the Environment: Define the JDK (e.g., JDK 8 or 17), OS (Windows, Linux, FreeBSD), and Maven command (e.g., mvn clean verify -DskipTests).
  • Use Docker: The script creates a Docker image with the exact environment (JDK, OS, Maven version) to ensure consistency.
  • Compare Binaries: The script downloads the reference binary and checks if the rebuilt binary matches, reporting success or failure.

Hervé demonstrated this with the Maven Javadoc Plugin (version 3.5.0), showing a 100% reproducible build when the environment matched the original (e.g., JDK 8 on Windows).

Step 2: Diagnose Differences

If the binaries don’t match, use diffoscope, a tool from the Linux reproducible builds community, to analyze differences. Diffoscope compares archives (e.g., JARs), nested archives, and even disassembles bytecode to pinpoint issues like:

  • Timestamps: JARs include file timestamps, which vary by build time.
  • File Order: ZIP-based JARs don’t guarantee consistent file ordering.
  • Bytecode Variations: Different JDK major versions produce different bytecode, even for the same target (e.g., targeting Java 8 with JDK 17 vs. JDK 8).
  • Permissions: File permissions (e.g., group write access) differ across environments.

Hervé showed a case where a build failed due to a JDK mismatch (JDK 11 vs. JDK 8), which diffoscope revealed through bytecode differences.

Step 3: Configure Maven for Reproducibility

To make builds reproducible, address common sources of “noise” in Maven projects:

  • Fix Timestamps: Set a consistent timestamp using the project.build.outputTimestamp property, managed by the Maven Release or Versions plugins. This ensures JARs have identical timestamps across builds.
  • Upgrade Plugins: Many Maven plugins historically introduced variability (e.g., random timestamps or environment-specific data). Hervé contributed fixes to numerous plugins, and his artifact:check-buildplan goal identifies outdated plugins, suggesting upgrades to reproducible versions.
  • Avoid Non-Reproducible Outputs: Skip Javadoc generation (highly variable) and GPG signing (non-reproducible by design) during verification.

For example, Hervé explained that configuring project.build.outputTimestamp and upgrading plugins eliminated timestamp and file-order issues in JARs, making builds reproducible.

Step 4: Test Locally

Before scaling, test reproducibility locally using mvn verify (not install, which pollutes the local repository). The artifact:compare goal compares your build output to a reference binary (e.g., from Maven Central or an internal repository). For internal projects, specify your repository URL as a parameter.

To test without a remote repository, build twice locally: run mvn install for the first build, then mvn verify for the second, comparing the results. This catches issues like unfixed dates or environment-specific data.

Step 5: Scale and Report

For large-scale verification, adapt Hervé’s reproducible-central scripts to your internal repository. These scripts generate reports with group IDs, artifact IDs, and reproducibility scores, helping track progress across releases. Hervé’s stats (e.g., 100% reproducibility for some projects, partial for others) provide a model for enterprise reporting.

Challenges and Lessons Learned

Hervé shared several challenges and insights from his journey:

  • JDK Variability: Bytecode differs across major JDK versions, even for the same target. Always match the original JDK major version (e.g., JDK 8 for a Java 8 target).
  • Environment Differences: Windows vs. Linux line endings (CRLF vs. LF) or file permissions (e.g., group write access) can break reproducibility. Docker ensures consistent environments.
  • Plugin Issues: Older plugins introduced variability, but Hervé’s contributions have made modern versions reproducible.
  • Unexpected Findings: Reproducible builds uncovered sensitive data in Maven Central artifacts, highlighting the need for careful build hygiene.

One surprising lesson came from file permissions: Hervé discovered that newer Linux distributions default to non-writable group permissions, unlike older ones, requiring adjustments to build recipes.

Interactive Learning: The Quiz

Hervé ended with a fun quiz to test the audience’s understanding, presenting rebuild results and asking, “Reproducible or not?” Examples included:

  • Case 1: A Maven Javadoc Plugin 3.5.0 build matched the reference perfectly (reproducible).
  • Case 2: A build showed bytecode differences due to a JDK mismatch (JDK 11 vs. JDK 8, not reproducible).
  • Case 3: A build differed only in file permissions (group write access), fixable by adjusting the environment (reproducible with a corrected recipe).

The quiz reinforced a key point: reproducibility requires precise environment matching, but tools like diffoscope make debugging straightforward.

Getting Started

Ready to make your Maven builds reproducible? Follow these steps:

  1. Clone reproducible-central and explore Hervé’s scripts and stats.
  2. Run mvn artifact:check-buildplan to identify and upgrade non-reproducible plugins.
  3. Set project.build.outputTimestamp in your POM file to fix JAR timestamps.
  4. Test locally with mvn verify and artifact:compare, specifying your repository if needed.
  5. Scale up using rebuild.sh and Docker for consistent environments, adapting to your internal repository.

Hervé encourages feedback to improve his tools, so if you hit issues, reach out via the project’s GitHub or Apache’s community channels.

Conclusion

Reproducible builds with Maven are not only achievable but transformative, offering security, trust, and operational benefits. Hervé Boutemy’s work demystifies the process, providing tools, scripts, and a clear roadmap to success. From preventing backdoors to catching configuration errors and sensitive data leaks, reproducible builds are a must-have for modern Java development.

Start small with artifact:check-buildplan, test locally, and scale with reproducible-central. As Hervé’s 3,500+ rebuilds show, the Java community is well on its way to making reproducibility the norm. Join the movement, and let’s build software we can trust!

Resources

PostHeaderIcon How to compile both Java classes and Groovy scripts with Maven?

Case

Your project includes both Java classes and Groovy scripts. You would like to build all of them at the same time with Maven: this must be possible, because, after all, Groovy scripts are run on a Java Virtual Machine.

Solution

In your pom.xml, configure the maven-compiler-plugin as follows:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <compilerId>groovy-eclipse-compiler</compilerId>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.codehaus.groovy</groupId>
                        <artifactId>groovy-eclipse-compiler</artifactId>
                        <version>2.8.0-01</version>
                    </dependency>
                    <dependency>
                        <groupId>org.codehaus.groovy</groupId>
                        <artifactId>groovy-eclipse-batch</artifactId>
                        <version>2.1.5-03</version>
                    </dependency>
                </dependencies>
            </plugin>

With such setup, default compiler (which cannot compile Groovy scripts parallelly of Java sources) will be replaced with Eclipse’s one (which can).

PostHeaderIcon (long tweet) Error injecting: org.codehaus.groovy.eclipse.compiler.GroovyEclipseCompiler

Case

On building a Groovy project with Maven, I got the following error:

Error injecting: org.codehaus.groovy.eclipse.compiler.GroovyEclipseCompiler
java.lang.NoClassDefFoundError: org/codehaus/plexus/compiler/CompilerResult

Quick fix

Downgrade maven-compiler-plugin to version 3.0 instead of branch 2.3.X.