A developer working on a laptop with a Git repository open.

Have you ever wanted to work with Git in Java? Did you know there’s a library for that? It’s called JGit!

I recently had to work with a Git repository that is hosted on GitHub from within a Java application and came across JGit. JGit is an open-source, Java implementation of Git that allows you to interact with Git repositories programmatically.

This post shares my insights and experiences with JGit, aiming to simplify its adoption for others. This is the first part of a two-part tutorial on using JGit. In part 1, I’ll cover what JGit is and what you can do with it, including how to open an existing repository on your machine, add files to it, commit changes, and push those changes back to the remote repository. We’ll also look at how authentication works when interacting with a remote repository.

This tutorial assumes you have a basic understanding of Git and Java, and the examples provided have been written using a Mac, so some commands may differ if you’re using a different operating system.

So what are you waiting for‽ — Let’s get started!


What can you do with JGit?

In short, pretty much everything you can do with the Git CLI. It is a battle tested library that powers (among other things) the Git integration in the widely used Eclipse IDE.

For example, with JGit you can:

  • Create a new repository or clone an existing repository
  • Open an existing repository
  • Stage/Add changes
  • Commit changes
  • Push changes
  • Pull changes


Setup

To use JGit, we need to include JGit’s Core Library in our project:

e.g. in Gradle

dependencies {
    implementation 'org.eclipse.jgit:org.eclipse.jgit:7.0.0.202409031743-r'
}


Basic concepts of JGit

JGit uses static helper methods to configure itself, and the main object you work with is the Git object, which represents a git repository.

For example, to work with an existing repository on disk, we call Git.open(someDirectory) which returns an instance of a Git object to work with provided the directory is a valid Git repository (contains a .git directory).

N.B. The Git object is a Closable resource, so it needs to be closed after use.

Once we have a Git object, we can build command objects that represent the various Git operations we want to perform which we can call to later execute them.

For example, to check the status of a repository, we call the git.status() method to build a StatusCommand object and then call the call() method on the command object to execute the command and return the status of the repository.

   StatusCommand statusCommand = git.status();
   Status status = statusCommand.call(); 
   boolean hasUncommittedChanges = status.hasUncommittedChanges();


Putting it all together

Given there exists a Git repository on our local machine at ~/my-repo, to do the equivalent of these bash commands:

$ cd ~/my-repo
$ git status 
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
  new file:   new-file.txt

We would do the following in JGit:

File localGitRootDirectory = Path.of("./my-repo").toFile();

try (Git git = Git.open(localGitRootDirectory)) {
    System.out.println("Has uncommitted changes: " + 
                       git.status().call().hasUncommittedChanges())
}

Another useful method on the Status object is isClean(), which checks for both uncommitted changes and untracked files. For more information on the Status class and its methods, see the Javadoc.


Staging changes (git add)

To do the equivalent of this bash command:

$ git add .

In JGit, we use the AddCommand object built by the git.add() method.

Some command objects in JGit require configuration before they can be executed; the AddCommand object requires a file pattern which tells it which files to stage before calling the call() method to execute the command.

So, to stage changes in JGit, we would do:

git.add()
  .addFilepattern(".")
  .call();


Committing changes (git commit)

To do the equivalent of this bash command:

$ git commit -m "Add new file to repository from command line"

In JGit, we use the CommitCommand object built by the git.commit() method.

The CommitCommand can be configured with a commit message and committer details using a PersonIdent object before calling the call() method to execute the command.

So, to commit changes in JGit, we would do:

git.commit()
    .setCommitter(new PersonIdent("David Davies", "david.davies@example.com"))
    .setMessage("Add new file to repository with JGit")
    .call();


Interacting with a remote Git repository

Up until now, we’ve been looking at Git operations that work on a local repository. But commands such as push, pull, and clone also interact with a remote repository, which introduces the need to manage the remote URL and any required authentication.

Authenticating with a remote

Most remote repos will require authentication (at least for the push command). In this tutorial, we’ll be working with remote repositories hosted on GitHub, which has two common authentication methods:

  • Using a personal access token (PAT) for authentication over HTTPS
  • Using SSH keys for authentication over SSH

To keep things simple in this tutorial, we’ll only be covering HTTPS authentication; SSH is more complex and will be covered in part 2 of this two-part blog post.

So in the following examples, we’ll be using a personal access token (PAT) for authentication via HTTPS. For more information on creating a PAT token, see the GitHub docs.


Providing Credentials for Authentication

The JGit command objects for operations such as git push, git pull, and git clone all share a setCredentialsProvider method that allows us to provide credentials to authenticate with the remote repository.

The setCredentialsProvider method takes a CredentialsProvider instance as its parameter. This interface has many implementations, the one we need to use for a PAT token is the UsernamePasswordCredentialsProvider (more commonly used for basic authentication).

Constructing a CredentialsProvider for a PAT token

The UsernamePasswordCredentialsProvider’s constructor requires a username and password. When using a PAT token, we pass the token as the username and an empty string as the password:

CredentialsProvider credentialsProvider = 
        new UsernamePasswordCredentialsProvider("OUR PAT TOKEN HERE", "");


Pushing changes to a remote repository (git push)

To do the equivalent of this bash command:

$ git push

In JGit, we use the PushCommand object built by the git.push() method.

The PushCommand object can be configured with a CredentialsProvider to authenticate with a remote repository before calling the call() method to execute the command.

So, to push changes to a remote repository in JGit, we would do:

git.push()
    .setCredentialsProvider(
        new UsernamePasswordCredentialsProvider("OUR PAT TOKEN HERE", ""))
    .call();

The same principle applies when doing a git pull operation.


Cloning a remote repository (git clone)

To do the equivalent of these bash commands:

$ cd ~/
$ git clone https://github.com/my-username/my-repo.git

In JGit, we use the CloneCommand object built by the Git.cloneRepository() method.

The CloneCommand object requires the URI of the remote repository and the local directory we want to clone into.

If the remote repository requires authentication for cloning, we also need to set a CredentialsProvider.

When called, this will return a Git object that we can use to interact with the cloned repository (which needs to be closed when finished with).

So, to clone a remote repository in JGit to directory ~/my-repo, we would do:

File directoryToCloneTo = Path.of("./my-repo").toFile();

try (Git git = Git.cloneRepository()
        .setDirectory(directoryToCloneTo)
        .setURI("https://github.com/my-username/my-repo.git")
        .setCredentialsProvider(
                new UsernamePasswordCredentialsProvider("OUR PAT TOKEN HERE", "")) // can be omitted if auth not required
        .call()) {
    
    // We can now execute commands on the Git object
}


Wrapping up with a complete example

Here’s a complete example that demonstrates how to clone a remote repository, add a new file to it, commit the changes, and push them back to the remote repository with both the Git CLI and JGit.

In bash, we would do:

$ cd ~/
$ git clone https://github.com/my-username/my-repo.git
$ cd my-repo
$ echo "Hello! From the command line" > hello.txt
$ git add hello.txt
$ git commit -m "Add hello.txt file to repository from command line"
$ git push

To do the equivalent in JGit, we would do:

public static void main(String[] args) {
    try {
        cloneStageChangesAndPushRepository();
    } catch (GitAPIException e) {
        System.err.println("An error occurred while interacting with the Git API: " + e);
    }
}

private static void cloneStageChangesAndPushRepository() throws GitAPIException {
    // Clone the remote repository under the user's home directory
    Path directoryToCloneTo = Path.of(System.getProperty("user.home"), "my-repo");

    CloneCommand cloneCommand = Git.cloneRepository()
            .setURI("https://github.com/my-username/my-repo.git")
            .setDirectory(directoryToCloneTo.toFile())
            .setCredentialsProvider(
                    new UsernamePasswordCredentialsProvider("OUR PAT TOKEN HERE", "")); // can be omitted if auth not required

    // try with resources to ensure the Git object is closed
    try (Git git = cloneCommand.call()) {

        // Create a new file and write to it
        Path helloFilePath = Paths.get(directoryToCloneTo.toString(), "hello.txt");
        try {
            Files.writeString(helloFilePath, "Hello! From JGit");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // Add the file to the repository
        git.add()
                .addFilepattern(".")
                .call();

        // Commit the changes
        git.commit()
                .setCommitter(new PersonIdent("David Davies", "david.davies@example.com"))
                .setMessage("Add hello.txt file to repository with JGit")
                .call();

        // Push the changes
        git.push()
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider("OUR PAT TOKEN HERE", ""))
                .call();
    }
}


Conclusion

In this tutorial, we introduced the Git object from JGit, which represents a Git repository and allows us to perform various Git operations programmatically. We covered essential Git commands such as status, add, commit, push, and clone. Additionally, we discussed how to authenticate with a remote repository using a personal access token (PAT) over HTTPS.

I hope you found this tutorial helpful and that it simplifies your adoption of JGit.

In the next part of this tutorial, we’ll be looking at how to interact with a remote repository using SSH keys for authentication.


Further reading

If you’re interested in learning more about JGit, I recommend checking out the following resources that I found helpful:



Enjoyed that? Read some other posts.