There are many frameworks that allow you to build REST easily in Java. In this blog, however, I am going tho show you how to build a standalone REST application without any framework. We are going to use the following libraries:
- Jetty for web server
- Jersey for REST
- Jackson for JSON
- Swagger for API documentation
First we need the build script. I am going to use Gradle for this.
plugins {
id "com.github.johnrengelman.shadow" version "2.0.1"
id "java"
}
apply plugin: "java"
apply plugin: "com.github.johnrengelman.shadow"
defaultTasks = ["clean", "build", "shadowJar"]
repositories {
jcenter()
}
dependencies {
// Jetty dependencies
compile "org.eclipse.jetty:jetty-server:9.4.6.v20170531"
compile "org.eclipse.jetty:jetty-servlet:9.4.6.v20170531"
// Jersey dependencies
compile "org.glassfish.jersey.core:jersey-server:2.25.1"
compile "org.glassfish.jersey.media:jersey-media-json-jackson:2.25.1"
compile "org.glassfish.jersey.containers:jersey-container-servlet:2.25.1"
// Swagger dependencies
compile "io.swagger:swagger-core:1.5.15"
compile "io.swagger:swagger-annotations:1.5.15"
compile "io.swagger:swagger-jersey2-jaxrs:1.5.15"
// Required by BeanConfig
runtime "org.codehaus.groovy:groovy:2.4.12"
// Required by Swagger
runtime "ch.qos.logback:logback-core:1.2.3"
runtime "ch.qos.logback:logback-classic:1.2.3"
}
jar {
manifest {
attributes "Main-Class": "org.fredy.example.rest.HelloServer"
}
}
Second, we will write a typical JAX-RS application with JAX-RS, Jackson, and Swagger annotations.
package org.fredy.example.rest;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Api(value = "/hello", description = "API for Hello")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/hello")
public class HelloResource {
@ApiModel("request")
public static class HelloRequest {
@ApiModelProperty(value = "The name", required = true)
private final String name;
@JsonCreator
public HelloRequest(@JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
@ApiModel("response")
public static class HelloResponse {
@ApiModelProperty(value = "The message", required = true)
private final String message;
@JsonCreator
public HelloResponse(@JsonProperty("message") String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
@ApiOperation(value = "Gets the message", response = HelloResponse.class)
@ApiResponse(code = 200, message = "Successful operation")
@Path("/message")
@POST
public Response getMessage(HelloRequest request) {
return Response.ok(new HelloResponse("Hello, " + request.getName())).build();
}
}
Third, we will need to register our REST resource into Jetty and start the server.
package org.fredy.example.rest;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.Resource;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.servlet.ServletContainer;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.util.StringJoiner;
public class HelloServer {
public static class Bootstrap extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setTitle("Hello API");
beanConfig.setVersion("1.0.0");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setBasePath("/api");
beanConfig.setResourcePackage("org.fredy.example.rest");
beanConfig.setScan(true);
}
}
public static void main(String[] args) throws Exception {
ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletHandler.setContextPath("/");
ServletHolder jerseyServlet = servletHandler.addServlet(ServletContainer.class, "/api/*");
jerseyServlet.setInitOrder(0);
jerseyServlet.setInitParameter(
ServerProperties.PROVIDER_CLASSNAMES,
new StringJoiner(",")
.add(HelloResource.class.getCanonicalName())
.add(JacksonJsonProvider.class.getCanonicalName())
.add(ApiListingResource.class.getCanonicalName())
.add(SwaggerSerializers.class.getCanonicalName())
.toString());
ResourceHandler staticHandler = new ResourceHandler();
staticHandler.setWelcomeFiles(new String[]{"index.html"});
staticHandler.setBaseResource(Resource.newClassPathResource("/webapp"));
HandlerList handlers = new HandlerList();
// Add two handlers, one for static content and the other one for dynamic content.
handlers.setHandlers(new Handler[]{staticHandler, servletHandler});
// The mapping doesn't really matter here.
ServletHolder swaggerServlet = servletHandler.addServlet(Bootstrap.class, "/");
swaggerServlet.setInitOrder(2);
Server jettyServer = new Server(8080);
jettyServer.setHandler(handlers);
try {
jettyServer.start();
jettyServer.join();
} finally {
jettyServer.destroy();
}
}
}
Few important things to note.
- Add the Jersey servlet container (org.glassfish.jersey.servlet.ServletContainer). This servlet implementation comes from Jersey library.
- Register some providers, such as our own REST provider (org.fredy.example.rest.HelloResource), Jackson provider (com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider), and Swagger providers (io.swagger.jaxrs.listing.ApiListingResource and io.swagger.jaxrs.listing.SwaggerSerializers). The Swagger providers will create swagger.json specified by the servlet mapping path, which is api/swagger.json in this case.
Lastly, we need to add some Swagger UI so that we can display the API documentation in a nice format. What we need is basically download Swagger UI from
https://github.com/swagger-api/swagger-ui and copy the dist directory into src/main/resources. The goal of this is to keep the Swagger UI in the JAR and is available in the classpath. Swagger UI is just a static content and we can tell Jetty to serve static content by using org.eclipse.jetty.server.handler.ResourceHandler. Make sure to modify the URL in Swagger's index.html from http://petstore.swagger.io/v2/swagger.json to http://localhost:8080/api/swagger.json since that's where our swagger.json file lives.
To build and run the example, we can type the following command.
./gradlew && java -jar build/libs/java-rest-swagger-example-all.jar
Full source code can be found
here.