DEV Community

Cover image for Java Enterprise 101: Building a REST Server with Spring Boot
Martin Häusler
Martin Häusler

Posted on

Java Enterprise 101: Building a REST Server with Spring Boot

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 ;)

Discussion (6)

Collapse
hcnenseth profile image
Hans Christian Nenseth

I am trying this out but get a compile error at the first build

gradlew.bat build

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':compileJava'.

    Cannot find System Java Compiler. Ensure that you have installed a JDK (not just a JRE) and configured your JAVA_HOME system variable to point to the according directory.

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

  • Get more help at help.gradle.org

BUILD FAILED in 2s
1 actionable task: 1 executed

Any ideas??

Collapse
martinhaeusler profile image
Martin Häusler Author

So usually this error means that gradle cannot find your Java Compiler, either because it is not installed at all or your PATH variable (windows) or JAVA_HOME (windows + linux) is not set up properly.

Try running javac -version in the same shell you are running your gradle command in to see what version of the Java Compiler is being called. If that command produces an error, you know that the JDK is not set up properly or not properly registered with the OS. On windows, it usually suffices to add the path to the JDK installation to the PATH environment variable. As for Linux and Mac, sorry I don't know; but Google / Stackoverflow should hold the answer for that.

Collapse
hcnenseth profile image
Hans Christian Nenseth • Edited on

Hmmm.. doesnt seem to be the problem.
It finds javac 10.0.1

Thanks for the quick reply by the way!!

Thread Thread
martinhaeusler profile image
Martin Häusler Author

Ok then it is very likely the JAVA_HOME environment variable that is not set up. Gradle is unfortunately rather picky when it comes to the setup and the environment variables. Here's a tutorial on how to set it up on windows (as you use the *.bat file I assume that is what you need):

mkyong.com/java/how-to-set-java_ho...

If that still doesn't work, feel free to try Maven instead; for this tutorial it really does not matter.

Collapse
zerh profile image
Eliezer Herrera

Java Enterprise 101??? Spring is not Java Enterprise, it's another stack

Collapse
martinhaeusler profile image
Martin Häusler Author

JEE is technically another stack, yes. But the basuc patterns and principles are the same, even the terms are interchangeable. When I say "Java Enterprise" I mean "Java as it is being used in Enterprise Applications". I did this distinction in the previous article (which is linked in the beginning of this article), I felt that repeting it wouldn't benefit anyone. Apparently I was wrong.