Java Enterprise 101: Building a REST Server with Spring Boot

Martin Häusler - May 17 '18 - - Dev Community

Following up on the first part of Java Enterprise 101, this time around we will get our hands dirty and implement the JEE stack with the help of Spring.

What you will need to follow along:

  • A working installation of JDK 10 (or higher)
  • A working internet connection
  • Optionally, your favorite editor or IDE

Setting up the project

We start... well, at the start. At start.spring.io to be precise. This URL will lead you to the Spring Initializr - a handy little website where you can mix and match libraries and packages to suit your project. In our case, we want to have the Web, JPA and H2 packages:

Spring Initializr Setup

I also changed the project name from "com.example" to "org.example" as well as the project type from Maven to Gradle. You can also use Maven perfectly fine, but for this guide I will be using Gradle. Once you hit the "generate project" button, you will end up with a *.zip file. Unpack it to a directory of your choice; this will become our project directory. Inside, you will find:

  • build.gradle: this file contains your build configuration.
  • gradlew / gradlew.bat: Gradle Wrapper. Always use these, never "gradle" itself.
  • settings.gradle: basically your Gradle module structure. We won't touch this, at least for now.
  • .gitignore: a very basic configuration to exclude binaries and generated files.
  • src: the source directory for your code. Follows the maven project structure.

Let's have a look at the generated build.gradle script, shall we?



buildscript {
    ext {
        springBootVersion = '2.0.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'org.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-web')
    runtime('com.h2database:h2')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}


Enter fullscreen mode Exit fullscreen mode

As far as buildscripts go, this is actually quite readable. It first says that it uses the spring-boot-gradle-plugin and the Maven Central repository for fetching its dependencies. Not really surprising, is it?

In the next section we have a list of gradle plugins which are applied to our project. Feel free to replace the eclipse plugin with the plugin for your IDE instead (e.g. idea for IntelliJ users). If you stick to editor and command line, you don't need this at all.

In my version, I changed the sourceCompatibility to 1.10 so I can use the var keyword from Java 10. The build script also mentions the maven coordinates of our project. Be a good citizen and always stick to semantic versioning (SemVer)! In the final section, we see a list of our dependencies, both for compiling and for testing our project.

/!\ IMPORTANT:
In order to make this buildscript work properly with Java 9 and higher (modularized Java platform libs) you also need to add the following dependency:

compile('javax.xml.bind:jaxb-api')

We are not going to actually use JAXB, but you will get a NoClassDefFoundError if you are missing this dependency. Hopefully, the Spring Boot Team will add it to their starter packs in the near future.

The Spring Boot Starter packages are "meta-packages" which contain an opinionated selection of libraries for common tasks. For example:

  • spring-boot-starter-web contains an (embedded) Tomcat as an application container and Jackson for JSON handling
  • spring-bot-stater-data-jpa contains Hibernate as the JPA provider
  • spring-boot-starter-test contains JUnit

That's nice, but what if you don't agree with the choices of the Spring Boot team? Don't worry, they've got you covered. If Spring Boot detects the presence of another library, say TestNG, this library will automatically take precedence over the default. In many cases, you don't even have to configure anything manually to make this happen; just add the dependency in gradle and rebuild the project. If you want some more exotic alternatives that Spring doesn't recognize, you will have to do a little bit more work, but that's for another article. For now, let's make use of what we get out of the box.

Getting to work!

First of all, you should verify that your setup is working as intended. The best way to do so is to open a command prompt (yes, even windows cmd will do...) and navigate to your project folder (where build.gradle is located). Run the following command:



gradlew build


Enter fullscreen mode Exit fullscreen mode

Under windows cmd, you might need to use gradlew.bat instead. Anyways, this will:

  • download all required dependencies from maven central
  • compile your source files
  • execute all your tests

"But we ain't got no tests!" you say? Well, Spring Initializr always includes one particular test case that may seem a little odd at first sight, but actually makes a lot of sense. Open up the file DemoApplicationTests.java (if you called your app something other than Demo, replace it accordingly).



@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    @Test
    public void contextLoads() {
    }

}


Enter fullscreen mode Exit fullscreen mode

This test does exactly what the name implies: it asserts that the Spring context loads up correctly. If this basic test fails, it means that you have a severe issue with your Spring / Spring Boot Setup that needs to be sorted out before continuing on. We should be okay with our setup now.

The other class that is generated by the Spring Initializr for you is the DemoApplication.java class:



@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}


Enter fullscreen mode Exit fullscreen mode

This is your main class, the entry point into your server-side application.

/!\ IMPORTANT:
The package in which this class resides is considered to be the root package by Spring Boot. Make sure that all of your source code either resides in the root package, or one of its (direct or indirect) child packages, otherwise Spring Boot will not be able to locate your classes.

Our first Spring REST Endpoint

Ready to write some actual code? Create a Java file and place it in your root package (or a sub-package). I place mine in org.example.demo.rest:



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

@RestController
public class HelloWorldRestController {

    @RequestMapping(method = RequestMethod.GET, path = "/api/hello-world")
    @ResponseBody
    public String sayHello(){
        return "Hello World!";
    }

}


Enter fullscreen mode Exit fullscreen mode

There you go. That's all it takes to build a REST endpoint with Spring Boot. The @RestController annotation tells Spring Boot what to do with this class (later on in this tutorial series we wil see other examples as well). The method sayHello (the name does not matter in the slightest, as long as it is a valid Java identifier you can name it whatever you like) implements a REST endpoint. The @RequestMapping provides the information to Spring which incoming requests to route to this endpoint. In this case, we bind it to the resource path /api/hello-world and to the request method GET. With @ResponseBody, we tell Spring that this method produces a result that should be returned to the client as the body of the HTML response.

Booting it up

Now for the really awesome part. Go to your command line interface again and navigate to your project folder (if you haven't done so already). Run the gradlew build command once more. Upon success, navigate to the build/libs sub-directory. In there, you will find a single .jar file. Run this file with java as you normally would:



java -jar demo-0.0.1-SNAPSHOT.jar


Enter fullscreen mode Exit fullscreen mode

This will start up your application server for you. Wait, what? But we did not even install a Tomcat yet! The funny thing is: you don't need to. While you can deploy your Java Enterprise application now in the "traditional" way as a war file, you can also run it as a regular java application, because the application container is alreay embedded in your application. Once it has finished starting up, open up a web browser and navigate to:



http://localhost:8080/api/hello-world


Enter fullscreen mode Exit fullscreen mode

You should see our "Hello World!" message showing up.

Testing

We wouldn't be much of a developer in 2018 if we didn't write proper tests, now would we? Luckily, Spring makes it relatively easy for us to test our REST endpoints. However, some setup is required. I will expand our existing test class for simplicities sake (and omit the existing test case in the following listings). Here's the setup code we need:



package org.example.demo;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @Before
    public void setup(){
        this.mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .build();
    }

}


Enter fullscreen mode Exit fullscreen mode

This helps us to set up the MockMVC helper. There is a lot more to this class than it initially appears to be; we will explore in another tutorial why exactly this class is so awesome for testing Spring REST endpoints. For now, just consider it a nifty helper to formulate a REST request. Note that the set up code above can be moved to a testing base class; it's never going to change, and there is no need to repeat it in every test class. Let's have a look at the code in more detail.

@Autowired is the dependency injection annotation used by Spring, very similar to @Inject used by JSR 330 implementations (e.g. Google Guice). With this, we basically tell Spring to pass in the single WebApplicationContext instance to our test. We then use JUnit's @Before method to set up our MockMvc instance using our web context. That is how the MockMvc instance "finds" your endpoints.

Now, let's write an actual test for our REST endpoint:



@Test
public void testHelloWorldEndpoint() throws Exception {
    var response = this.mvc.perform(
            get("/api/hello-world")
    ).andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
    assertEquals("Hello World!", response);
}


Enter fullscreen mode Exit fullscreen mode

This internal DSL allows us to perform the get request and we expect the HTTP status code to be ok, and we return the response content as a String. This reads like prose. Compiler-checked prose that reduces dozens of lines with assertions into a single line. If that isn't beautiful, I don't know what is.

Run this test and watch it pass! You can either do it in the command line with



gradlew test


Enter fullscreen mode Exit fullscreen mode

... (this runs all tests) or directly in your IDE.

Closing Thoughts

This has become quite a long article - a lot longer than I intended. However, if we consider how many lines of actual code we actually wrote (about 10?) it is hard to argue that Java is "such a verbose language", considering what we achieved in terms of functionality. We also have the foundations in place to extend our server with a data model and a database connection in the next article. We literally merely scratched the surface. Brevity aside, the true advantages of Spring Boot and the JEE architecture are not yet visible. Bear with me for now please ;)

. . . . . . . . . . . . . . . . . . . . . .