Java Programming in Emacs

Introduction

In this tutorial, I will go through setting up Emacs for Java development. The installation part will be fairly simple, as we will use my java specific Emacs settings. For this particular setup, I want to focus on Java Programming specific packages. Therefore, this setup does not contain other popular Emacs packages. Once you are comfortable with how it works, you should be able to add your favorite package to this setting or copy settings and ideas from here to your setup without any problem. We will use Language Server Protocol (LSP) related packages and settings for this setup. You can read more about LSP here. At the end of this setup you should have an editor that has a debugger UI, code auto-completion, project initializer and several other features to help you edit and manager your code. E.g.

Editor window showing code actions

Installation

You should have emacs installed in your computer. If not, go ahead and install it first. Also, make sure you have installed java, maven and git. Backup your current emacs folders and files. If later you don’t like this setup you can always revert back. After backing up, delete any .emacs or .emacs.d files/folders in your home directory. In a terminal window execute following commands. These commands are for unix environments. If you are on windows adjust the commands to match the directories accordingly.

$ git clone https://github.com/neppramod/java_emacs.git ~/.emacs.d

Note: If you want to test above configuration outside your primary setup, you can download it to a separate directory and load it using emacs -q -l init.el. However you will need to adjust EMACS_DIR variable value inside init.el file. Adjust it to where you have downloaded above setting. I prefer previous method, where you copy it to your primary emacs configuration directory.

I have tested this setting in java 10 and java 14. If you have older versions of java, you may need to download a newer version of java. Once downloaded you can specify them in operating system specific files, linux.el, mac.el or windows.el. Since I also have an older version of java in my Mac, I had to specify values to two variables (JAVA_HOME and lsp-java-java-path) to point to the newer java version. If emacs complains about java version during installation of LSP please add following settings to operating system specific files, and adjust the folders accordingly. It may not be a bad idea to set them up anyway.

(setenv "JAVA_HOME"  "path_to_java_folder/Contents/Home/")
(setq lsp-java-java-path "path_to_java_folder/Contents/Home/bin/java"

I have used DejaVu Sans Mono font for this setup. If you don’t have it installed, please install it too. You can change the font in the configuration file, or remove the line that sets the font. See the Font section in the wiki.

After above setup, you can go ahead and start emacs. It should download all the required packages and setup emacs accordingly. If at any point, it complains about missing packages, you may have to refresh to latest package list by executing package-list-packages in emacs and restart emacs. Once the installation is finished restart emacs once anyway. Now, you should have all the packages required for java development. Emacs should look like in the screenshot below.

After installation

You can switch between white and dark themes using F6 key. You can go through emacs-configuration.org and init.el to see how each packages are setup.

Next, we will tackle how to use LSP to work with java projects.

Working with Java Project.

You can work on a simple java project with few files that has main method in it. However, here let’s setup a little bit more than that. LSP comes with lsp-java-spring-initializer command, an emacs interface for start.spring.io (spring initializer). Go ahead and execute that command. Use following setup instructions.

group name: com.example
artifact id: demo
Description: Demo project for Spring Boot
Select boot-version: Select latest snapshot
Java Version: I selected 11, but you can others based on your setup 
Select Language: Java 
Select Packaging: Jar 
Select Package Name: com.example.demo 
Select type: Maven 
Project Select project directory: select a project directory
Select dependencies: Developer Tools / Spring Boot Dev Tools (Provides fast application restarts …). I selected first one from the list

At the end of the setup, emacs should ask Do you want to import the project ? Select yes. After importing the project, it should take you to the root of the project (where you will see pom.xml). If you type C-c p f you can quickly find the files within the project and select them by typing few characters.

Once you open a java file within the project, lsp-java should prompt you to install the server. Go ahead and install the available jdtls server. After that it should ask you to import the project root, import it to LSP. You can stop, start, restart, disconnect a lsp server by using C-c l and the options shown below. If you see error message while connecting the server, you may want to shutdown existing server, or start a new one if none exist.

Manage sessions

Let’s create a simple java class. Create a class called Person in com.example.demo package inside src/main/java/com/example/demo directory with following code.

package com.example.demo;

public class Person {
    private String name;
    private String title;

    public String getTitle() {
		return title;
    }

	public void setTitle(String title) {
		this.title = title;
    }

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}   

You can also use various functionalities of LSP to create above code. E.g. Let’s say you did not have getter/setter for name. You coulduse C-c l a a to provide option to create getter/setter for the filed. You could also click the option presented in the top right corner of the editor, just like in below diagram. If you select the general Getters and Setters option, you should be able to select more than one variable using Space key, and generate getters and setters for all of them. Helm helps you in selecting more than one entry from the list.

Use code actions

Also create a unit test class called PersonTest.java inside demo/src/test/java/com/example/demo/PersonTest.java with following code

package com.example.demo;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class PersonTest
{
    @Test
    public void testMethods() {
		Person p = new Person();
		p.setName("Monkey D. Luffy");
		p.setTitle("Pirate King");

		assertEquals(p.getName(), "Monkey D. Luffy");
		assertEquals(p.getTitle(), "Pirate King");
    }
}

Projectile recognizes a test file with Test prefix with the class name. If you have Person and PersonTest, you can quickly jump between each of them using C-c p t.

If you are in Person class and use M-? (Alt + ?), you can see see Person’s references in the project, You can go to them easily using C-n, C-p.

If you are highlighting Person type in PersonTest class, you can quickly go to the definition class (Person) using M-. You can jump to definition of methods, variables etc using this key. To go back to previous jump point using M-,

If you use C-c l g a you can search any symbols in the workspace (including standard java symbols) and quickly jump to source code of that type.

Search workspace symbols

To run the unit test use C-c p P command (projectile-test-project) and type “mvn test”. It should build the project and run the unit tests. You should see something like in the following diagram.

Note: I have changed the theme to white.

Projectile test project

If you want to run the project you can use C-c p u command (projectile-run-project) and type “mvn spring-boot:run”. Since there is nothing to run at this moment, it should start the DemoApplication and quit with Success.

For the next part, let’s create a simple web response using the example in Building an Application with Spring Boot. Since we selected the first option while creating our spring project, we may not have spring-web added to our project. Add following dependency in pom.xml

<dependency>		 
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Also for test dependency you can add the exclusions as mentioned in above wiki. Test dependency should look like following.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
	<exclusion>
	  <groupId>org.junit.vintage</groupId>
	  <artifactId>junit-vintage-engine</artifactId>
	</exclusion>
  </exclusions>
</dependency>

Now, let’s add a simple controller called HelloController.java into the project. Create a file called HelloController.java inside same folder where you have DemoApplication.java. We should have separated each part in separate packages, but for this tutorial we want to keep it simple.

  1. To add the package you can start by typing “package com” emacs, should complete rest of it.
  2. To add the class name, we can use yasnippets. Type “file” and press TAB. It should complete the class name for you.
  3. Let’s add an annotation called @RestController for the controller class. While you are tying above code, emacs should suggest you with the package name and when you press enter, it should add the package name to the file.
  4. Note that, if you hover over a type or method, it shows javadoc documentation.
  5. Use same technique for adding a string method that returns a message. Your class should look like the following code.
package com.example.demo;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

	@RequestMapping("/")
	public String index() {
		return "Greetings from Spring Boot!";
	}

}

If you now run the project with mvn spring-boot:run using C-c p u command launcher, you should notice the application no longer quits. If you visit http://localhost:8080/ you should see the greeting message we wrote in HelloController. In my case, I went through rest of the tutorial, and after adding actuator, I was able to see the health of the website, as in following diagram.

Completed Spring Tutorial

To quit the running server, press C-c C-k in the *compilation* window (window where you see the spring messages).

Debugging

There are various ways to debug your application. If you want to start from main, you can invoke dap-java-debug command to start debugging. In this example though this command will run the HelloController. Let’s instead test Person class. Open PersonTest class and invoke dap-java-debug-test-class. You can also test a single method by using dap-java-debug-test-method. You should see 1 test successful message. This is not very intuitive. Let’s add a break point at line 11 of PersonTest class. This is the next line to where Person class is initialized. Now if you invoke dap-java-debug-test-method, when your cursor is within the tetMethods() method, you should see the debug UI. There should be a hydra window at the bottom showing you keys for different options.

Debug Window

You can open up local variable list with sl key, similarly sb to see breakpoints, and you can jump through the code using n, i, o and c keys. If the hydra window disappears for some reason, you can always bring it back using M-F5 key (Alt + F5). Couple of useful commands I tend to use quite often are from Eval section. E.g. I can add an expression to watch it while I am debugging. I can use ea and add an expression from objects in the test. E.g. In below example, I added expressions like “p.getName()“, “p.getTitle()” and “person.getName()“. As you know we don’t have a person object, so that should show an error. Other two values should evaluate to null until we execute the setter lines for each of the variables. Also if you look closely in the diagram below, I selected p.getTitle() in code and used er to evaluate the selected region. Go through other options to see what they do.

Evaluating code at runtime

Conclusion

This tutorial should get you to a comfortable position where you can explore more options provided by LSP, Projectile, DAP and Helm to work through your project. To make your coding easier I suggest adding various snippets to complete your code. Also remember to manage LSP servers. If you start too many servers your computer memory might go up quickly. Take a look at memory usage, and restart LSP server or emacs if necessary. To make lsp start over again, you can delete the .lsp-session* files and workspace folder (last option) inside your .emacs.d folder. Also use projectile-remove-known-project or similar projectile commands to remove projects that are no longer needed. You can always add them later. If you feel your computer is sluggish with Emacs and LSP tweak gc-cons-threshold and gc-cons-percentage values in init.el. You can also tweak read-process-output-max variable in lsp-mode setting. Java uses around 1GB memory. You can tweak lsp-java-vmargs as listed in lsp-java github configuration file as well. I hope this tutorial helped you to start using Emacs for Java development.

References

  1. https://github.com/emacs-lsp/lsp-java
  2. https://github.com/emacs-lsp/lsp-ui
  3. https://emacs-lsp.github.io/lsp-mode/page/performance/
  4. https://spring.io/guides/gs/spring-boot/

Leave a comment

Filed under Uncategorized

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s