Working with Git in Java: Part 1 - A JGit Tutorial
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:
References and useful links
- Java documentation for the JGit library
- A good article on JGit Authentication
- GitHub’s documentation on personal access tokens
- JGit libraries on Maven Repository
- Official Eclipse JGit release notes
Enjoyed that? Read some other posts.