packaging, java

Building Debian Packages in Java

A truly platform independent way to build Debian packages from Maven, Gradle, or Ant - without native tools.

June 2025

Using native packaging formats to install Java applications leverages existing and well-tested infrastructure for software deployment. But one of the perks of Java has always been the cross-platform build. Using the standard native tools like dpkg-deb to create those packages breaks the promise of a platform independent build.

With the jdeb project there is a way to create Debian packages directly from your build - whether you use Maven, Gradle, or Ant.

Build Debian packages without native tools - on Linux, Windows, or macOS.

The project has been maintained since 2007 and is used by over 3,500 repositories on GitHub - including projects at Apache, Netflix, and Eclipse.

Why native packages in the container era?

With containers being everywhere, the question comes up whether native packages are still relevant. I think the answer is yes - for several reasons.

First, even inside containers you often want proper package management. A clean apt install with declared dependencies beats copying files around. System services, users, permissions, and config files are all things that packages handle well.

Second, not everything runs in containers. Bare-metal deployments, embedded systems, appliances, and developer tooling all benefit from native packages. And even when containers are the delivery vehicle, building the underlying image from proper packages keeps things reproducible.

You could of course use Docker to run the native packaging tools during the build. But that adds considerable complexity to what should be a straightforward Java build. The whole point of jdeb is to avoid that.

The control file

The only requirement is a control file that provides metadata about the package. It declares things like name, version, and most importantly dependencies.

Package: [[name]]
Version: [[version]]
Section: misc
Priority: optional
Architecture: all
Description: [[description]]
Maintainer: Your Name <you@example.com>
Depends: default-jre (>= 11)

jdeb also handles maintainer scripts (preinst, postinst, prerm, postrm) - just place them next to the control file in your control directory. These scripts are often the tricky part of packaging, and jdeb passes them through without getting in the way.

Maven

With Maven, jdeb attaches to the package phase. A build creates the jar, then builds the .deb and attaches it as a secondary artifact.

<plugin>
  <artifactId>jdeb</artifactId>
  <groupId>org.vafer</groupId>
  <version>1.14</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>jdeb</goal>
      </goals>
      <configuration>
        <controlDir>${basedir}/src/deb/control</controlDir>
        <dataSet>
          <data>
            <src>${project.build.directory}/${project.build.finalName}.jar</src>
            <type>file</type>
            <mapper>
              <type>perm</type>
              <prefix>/usr/share/myapp/lib</prefix>
              <user>loader</user>
              <group>loader</group>
              <filemode>640</filemode>
            </mapper>
          </data>
        </dataSet>
      </configuration>
    </execution>
  </executions>
</plugin>

Running mvn package now produces both the jar and the .deb.

Gradle

jdeb itself does not ship a Gradle plugin, but Netflix’s nebula ospackage plugin uses jdeb under the hood and integrates beautifully with Gradle’s copy spec.

plugins {
  id 'com.netflix.nebula.ospackage' version '12.2.0'
}

ospackage {
  packageName = 'myapp'
  version = '1.0'
  release = '1'

  into '/usr/share/myapp/lib'

  from(jar.outputs.files) {
    fileMode = 0640
    user = 'loader'
    permissionGroup = 'loader'
  }
}

Running gradle buildDeb creates the package.

Ant

For Ant, jdeb provides a task directly. Specify the paths and provide the data that should get included.

<taskdef name="deb"
  classname="org.vafer.jdeb.ant.DebAntTask"
  classpath="jdeb.jar" />

<deb
  destfile="${build.dir}/${ant.project.name}.deb"
  control="${build.dir}/deb/control"
  verbose="true">
  <data type="file"
    src="${build.dir}/jar/${ant.project.name}-${version}.jar">
    <mapper type="perm" prefix="/usr/share/jdeb/lib"/>
  </data>
</deb>

GitHub Actions

Since jdeb runs as part of your normal build, creating packages in CI is straightforward. No special actions or Docker containers needed.

- name: Build with Maven
  run: mvn package

- name: Upload .deb artifact
  uses: actions/upload-artifact@v4
  with:
    name: debian-package
    path: target/*.deb

That’s it. The .deb gets built on whatever runner GitHub provides - Linux, macOS, or even Windows.

Alternatives

Naturally, jdeb is not the only option. Depending on your setup, these might be worth considering:

Getting started

Check out the documentation and the examples on GitHub. Issues and discussions are welcome in the repository.

selected photo