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 manage your code. E.g.

Image
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"

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.

Image
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.

Image

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 messages while connecting to the server, you may want to shut down existing server and start the server again.

Image
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 field. 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.

Image
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.

Image
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.

Image
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 the 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.

Image
  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!";
	}

}
Image

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.

Image
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.

Image
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.

Image
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/

5 Comments

Filed under Uncategorized

5 responses to “Java Programming in Emacs

  1. Gustavo Bertolino's avatar Gustavo Bertolino

    Hi, how can I remove the lamp which appears in the code as part of code-actions suggestions? I was trying removing it but I didn’t succeed. Could you help me with this setup?

  2. This a great post! Thanks for writing this. I was able to get a working setup after going through this post. Much appreciated.

  3. Rocky Smith's avatar Rocky Smith

    I was unable to make sense of the installation instructions. The first paragraph: “You should have emacs installed in your computer.” Do statements like this really serve any purpose?

    You go on to give advice about “testing outside your primary setup.” I would leave such advice out. Anyone capable of understanding what this means, or who needs to do so, would address this individually, often with version control, modularization, or other means. You seem to assume that everyone using emacs has files called linux.el, mac.el, or windows.el. ??? What are you talking about?

    By the 3rd paragraph, you write: “I have tested this setting in java 10 and java 14.” I have no idea what setting you are referring to.

    For reasons I can’t fathom, in the middle of this, you tell the reader what font to use. Why are you giving this instruction? Does this font have special features that lsp-mode uses? Or do you just want everyone in the world to use the fonts you like best?

    You then instruct the reader to start emacs. I thought we were already using emacs. I also have no idea where or how java-lsp was supposed to be installed, and what, if any, configuration options it offers.

    And so on.

  4. This was very helpful, thank you.

    This was a lot of fun.
    I love the dual-use of the org file to create an init file.

    It will take some time to get the lay-of-the land workflow, but I am grateful I am not going to have to use eclipse or netbeans.

    I did not get the HelloController to display anything on the first pass, but I will figure that out by taking the tutorial at the link.

    Also, this is my jump from ECB to the helm sort of setup that is popular nowadays. (ECB is broken in 27.2)

    The LSP “just.makes.sense” maybe I can get around to writing one for Squeak Smalltalk one of these days and be able to code smalltalk in emacs…

    again, thank you.

Leave a comment