Skip to content

Project Setup

In order to start writing plugins, we first have to setup our project and our IDE. Most people use IntelliJ IDEA as their primary IDE. It provides all tools and plugins necessary for efficient Java development and also provides native support for our build tooling, Gradle, which is also the suggested build tool by the PaperMC team.

It is not necessary to install both Gradle and IntelliJ together, as IntelliJ includes Gradle. But if you prefer to setup projects using a CLI (Command Line Interface), you can also install Gradle separately.

Main article: https://www.jetbrains.com/help/idea/installation-guide.html

I personally recommend installing IntelliJ via the JetBrains Toolbox App, as it allows quick and easy updates of IntelliJ.

Once you have installed the JetBrains Toolbox App, you will want to install IntelliJ IDEA Community Edition.

JetBrains Toolbox App

After you have installed IntelliJ, you should open it and then open the settings screen by pressing CTRL + ALT + S. Navigate to the Plugins tab and install the Minecraft Development plugin. The plugin provides many useful QoL features for both plugin and mod devs.

Installing the Minecraft Development plugin

When creating a new project with IntelliJ, you will want to create a Java project. After that, you can fill out most fields however you want. Here is a quick breakdown of what each field does:

Field NameUsage
NameThis is the name of your project/plugin. I will be putting in BeginnerPlugin.
LocationWhere the project will be located. I usually put my projects under C:\Dev\ (~/dev/ when on Linux).
🔳 Create Git repositoryWhether to initialize a git repository. You have to install git for this to work.
Build systemWe will be using Gradle throughout this whole guide.
JDKMake sure to choose/install JDK 21, as that is the version required for developing plugins past version 1.20.5.
Gradle DSLWe will be using Kotlin, so make sure to choose that.
🔳 Add sample codeWhether to include some basic starter code files. You should disable this, as we will be building everything up from the ground.
Gradle distributionYou should choose Wrapper as that allows your project to be build from the command line even without having Gradle installed.
Gradle versionIt is good practice to keep this up-to-date. Make sure to choose at least 8.12.
GroupIdThis is essentially an unique identifier for your projects. It typically represents a domain in the reverse order. You may choose any group id, but it is good practice to choose one where you also own the domain.
ArtifactIdThis setting doesn’t affect anything with plugin development. You can just set it to your project name.

IntelliJ New Project Window

The project files should look like this now:

  • DirectoryBeginnerPlugin
    • Directory.gradle/
    • Directory.idea/
    • Directorygradle/
    • Directorysrc
      • Directorymain/
      • Directorytest/
    • build.gradle.kts
    • gradlew
    • gradlew.bat
    • settings.gradle.kts

You can safely delete the src folder for now, as it contains stuff which we do not need yet.

We may have created the project now, but we are not done yet! There is still a few things missing. For once, our build.gradle.kts file doesn’t contain anything particularly useful. You should replace all of the content in it with the following:

build.gradle.kts
plugins {
id("java")
}
group = "com.learnpaperdev.beginner" // Replace with your own id
version = "1.0-SNAPSHOT" // Our project version
repositories {
// You can declare remote repositories here
}
dependencies {
// You can declare dependencies here
}

You may be wondering: “What are dependencies and where do we get them from?” Basically, a dependency is just another Java project, which you compile against. That means you can use their code inside your own code. There are multiple types of dependencies, but the only relevant one for us for now is a compile-time-only dependency, which means that the other’s code is not included in our own. You will learn more about how this exactly works later, but for now, we will only be using compileOnly(...).

Furthermore, there is two main ways on how you can depend on an other’s code. The first is using a local file dependencies — that means you just include their compiled or source code in your project. You can declare a file dependency like this:

build.gradle.kts
dependencies {
compileOnly(files("path/to/dependency/file", "other/path", "..."))
}

The second possible dependency is a remote dependency, which is downloaded from the internet. Those are declared like this:

build.gradle.kts
dependencies {
// v Maven Coordinates v
compileOnly("group.io:project-id:version")
}

The notation inside the compileOnly(...) function is called maven coordinates. In simple terms, you could say that the group.io refers to “who”, the project-id to “what”, and the version to the version.

If we want to compile against the Paper API, we have to declare our dependency like this:

build.gradle.kts
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
}

As said before, io.papermc.paper refers to the group id of Paper, paper-api is the project id of the Paper API, and 1.21.4-R0.1-SNAPSHOT is the version of the server we will primarily be compiling against (1.21.4). The -R0.1-SNAPSHOT part is something that has been part of the version for a really long time from way before Paper times. It has its reasons, but there is not need to go into detail on that for now.

If you were to try to compile the project right now, it would still not include the Paper API, as Gradle doesn’t know where to look — the internet is huge after all. For this we have the repositories block. Here, we declare all paths where Gradle should look for dependencies.

Paper’s repository is referred to with https://repo.papermc.io/repository/maven-public/. If we add this as an repository to our build file, our Paper API dependency will be found. That would look like this:

build.gradle.kts
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}

Your build file should look like this now:

build.gradle.kts
plugins {
id("java")
}
group = "com.learnpaperdev.beginner" // Replace with your own id
version = "1.0-SNAPSHOT" // Our project version
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
}

Currently, there are no Java-source files in our project. We add those inside the src/main/java directory. You should create that directory now. The file structure should look like this:

  • DirectoryBeginnerPlugin
    • Directorysrc
      • Directorymain
        • Directoryjava/
    • build.gradle.kts

What is the reason for this obscure structure? What do you need main for? And why an explicit java directory? Well, the reason for the main directory is that you can also have stuff under src/test, which is typically the directory with all your unit test. This just helps separate files which should be compiled into the final output file from the test files. And the reason for the explicit java directory is that you can also code plugins in other languages! There is a lot of other JVM-based languages, like Groovy or Kotlin. Those would each have their own sub folder in the source folder. For example, for Kotlin you would choose src/main/kotlin.

Now, to create your first source file, we need even more directories. Inside the java directory, you should now create a few directories based on your group-id. In general, you separate your group-id by its dots and make each of them a directory.

For example, for com.learnpaperdev.beginner, you would create the following directories:

  • DirectoryBeginnerPlugin
    • Directorysrc
      • Directorymain
        • Directoryjava
          • Directorycom
            • Directorylearnpaperdev
              • Directorybeginner/
    • build.gradle.kts

The reason for this tedious amount of folders is so that we can effectively separate the files of different plugins. Imagine that Plugin A has these files in their src:

  • Directorysrc
    • Directorymain
      • Directoryjava
        • DirectoryAPlugin.java
          • PluginUtils.java

And Plugin B has these files in their src:

  • Directorysrc
    • Directorymain
      • Directoryjava
        • DirectoryBPlugin.java
          • PluginUtils.java

Now, both plugins declare a PluginUtils.java file. These may have different contents though. So how can the server, when loading both plugins, know which of these two PluginUtil.java files one of the plugins references? The answer is: It just cannot. But if we separate them into different folders, each plugin will always be able to reference its specific one.

So yeah, now that you have created your folder structure, you can create your first source file: BeginnerPlugin.java. The empty source file should look like this:

// Again, replace with your own group id — this should match the folder structure
package com.learnpaperdev.beginner;
public class BeginnerPlugin {
}

In the top of the sidebar in the right, notice the small Gradle (Elephant) icon. When you click on that, a menu should appear. When you now expand the entries BeginnerPlugin > Tasks > build you can see all of the tasks you can run. When you now double-click the build task, Gradle will now attempt to compile your plugin.

If successful, you should find the compiled file under builds\libs\BeginnerPlugin-1.0-SNAPSHOT.jar.

  • DirectoryBeginnerPlugin
    • Directorybuilds
      • Directorylibs
        • BeginnerPlugin-1.0-SNAPSHOT.jar

Finally, after a lot of steps, we got a project up and running! Yet, if you were to drop this into the plugins folder of a Paper server, it wouldn’t do anything. That is what we will be tending to next. After all, this is a Paper plugin guide, and not an IntelliJ guide.

Learn Paper Dev is licensed under CC BY-NC-SA 4.0