Spring Boot allows us to create RESTful web-services with ease, Swagger specifies a format to describe the capabilities and operations of these services and with Swagger UI it is possible to explore our REST API with a nice graphical user interface in our browser.
Springfox is a project that aims at creating automated JSON API documentation for API’s built with Spring and is used in the following tutorial to integrate Swagger into a sample application.
Creating our RESTful Webservice with Spring Boot
With Spring Boot our RESTful web-service is set up in no time.
Our restful web-service allows a user to return the current date in a customized format as a JSON structure, in addition an increasing counter is returned, too.
An example: If the user specifies the format yyyy, our webservice returns the current year with 4 digits e.g. 2015.
For further details about creating RESTful web-services with Spring Boot I highly recommend reading the excellent tutorial from Spring.io: “Building a RESTful Webservice“.
Gradle Configuration
This is our build.gradle.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.4.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'dateconv-rest-service'
version = '1.0.0'
}
repositories {
mavenCentral()
jcenter()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("io.springfox:springfox-swagger2:2.0.2")
compile("io.springfox:springfox-swagger-ui:2.0.2")
testCompile("junit:junit")
}
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
}
DateFormat Value Object
This POJO encapsulates the formatted date, the counter and the user’s date pattern.
package com.hascode.tutorial;
public class FormattedDate {
private final long id;
private final String dateString;
private final String pattern;
public FormattedDate(final long id, final String pattern, final String dateString) {
this.id = id;
this.pattern = pattern;
this.dateString = dateString;
}
// getters ommitted
}
Resource Controller
The controller defines a RESTful web-service with an entry point URL pattern “/currentdate/{pattern}” where pattern marks the path parameter to specify the desired date-format.
Finally the controller creates and returns a new value object with the formatted date:
package com.hascode.tutorial;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CurrentDateController {
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/currentdate/{pattern}")
public FormattedDate formatCurrentDate(@PathVariable final String pattern) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
return new FormattedDate(counter.incrementAndGet(), pattern, fmt.format(LocalDateTime.now()));
}
}
Spring Application Container
All we need to run the RESTful web-service is the main method, the other configuration is used to setup swagger for our application.
With the additional code we use Springfox to..
-
enable Springfox Swagger 2
-
specify where the Spring API controllers are located
-
setup a new Docket (Docket is Springfox primary configuration mechanism)
-
setup paths, security, API tokens and all the stuff
As I don’t want to copy and paste, I’d highly recommend to have a look at the Springfox Reference Documentation where everything is explained very nice and in detail.
package com.hascode.tutorial;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
import java.time.LocalDate;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import com.fasterxml.classmate.TypeResolver;
@SpringBootApplication
@EnableSwagger2
@ComponentScan(basePackageClasses = { CurrentDateController.class })
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Docket petApi() {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build().pathMapping("/")
.directModelSubstitute(LocalDate.class, String.class).genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(newRule(typeResolver.resolve(DeferredResult.class, typeResolver.resolve(ResponseEntity.class, WildcardType.class)), typeResolver.resolve(WildcardType.class)))
.useDefaultResponseMessages(false)
.globalResponseMessage(RequestMethod.GET, newArrayList(new ResponseMessageBuilder().code(500).message("500 message").responseModel(new ModelRef("Error")).build()))
.securitySchemes(newArrayList(apiKey())).securityContexts(newArrayList(securityContext()));
}
@Autowired
private TypeResolver typeResolver;
private ApiKey apiKey() {
return new ApiKey("mykey", "api_key", "header");
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex("/anyPath.*")).build();
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return newArrayList(new SecurityReference("mykey", authorizationScopes));
}
@Bean
SecurityConfiguration security() {
return new SecurityConfiguration("test-app-client-id", "test-app-realm", "test-app", "apiKey");
}
@Bean
UiConfiguration uiConfig() {
return new UiConfiguration("validatorUrl");
}
}
Directory Structure
Our project structure should look like this one by now:
.
├── build.gradle
└── src
└── main
└── java
└── com
└── hascode
└── tutorial
├── Application.java
├── CurrentDateController.java
└── FormattedDate.java
Exploring the Application
Now we’re ready to boot our web-service and explore Swagger and Swagger UI…
Starting with Gradle
Using Gradle, out application is booted within a few seconds using the following command:
$ gradle bootRun
[..]
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.4.RELEASE)
2015-07-01 08:27:50.673 INFO 13044 --- [ main] com.hascode.tutorial.Application : Starting Application on styx with PID 13044 (/data/project/spring-boot-swagger-tutorial/build/classes/main started by soma in /data/project/spring-boot-swagger-tutorial)
[..]
2015-07-01 08:27:52.765 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/currentdate/{pattern}],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public com.hascode.tutorial.FormattedDate com.hascode.tutorial.CurrentDateController.formatCurrentDate(java.lang.String)
2015-07-01 08:27:52.766 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v2/api-docs],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<springfox.documentation.spring.web.json.Json> springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(java.lang.String)
2015-07-01 08:27:52.769 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/configuration/security],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto org.springframework.http.ResponseEntity<springfox.documentation.swagger.web.SecurityConfiguration> springfox.documentation.swagger.web.ApiResourceController.securityConfiguration()
2015-07-01 08:27:52.769 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/configuration/ui],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto org.springframework.http.ResponseEntity<springfox.documentation.swagger.web.UiConfiguration> springfox.documentation.swagger.web.ApiResourceController.uiConfiguration()
2015-07-01 08:27:52.769 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/swagger-resources],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto org.springframework.http.ResponseEntity<java.util.List<springfox.documentation.swagger.web.SwaggerResource>> springfox.documentation.swagger.web.ApiResourceController.swaggerResources()
2015-07-01 08:27:52.772 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-07-01 08:27:52.773 INFO 13044 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
[..]
2015-07-01 08:27:53.201 INFO 13044 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-07-01 08:27:53.203 INFO 13044 --- [ main] com.hascode.tutorial.Application : Started Application in 2.723 seconds (JVM running for 3.036)
Afterwards all necessary services are available to us..
Calling the RESTful Webservice
First of all we’d like to try our RESTful web-service, available at http://localhost:8080/currentdate/PATTERN where PATTERN is a valid date format.
Examples:
Swagger Definition Export
Swagger creates a complete service documentation for us that may be used to generate REST-clients for our services in no time (e.g. using an online editor like Swagger Editor).
We may access the documentation in JSON format at: http://localhost:8080/v2/api-docs
Swagger API Definition image::springboot-swagger-definition-of-rest-service-1024x799.png[]
This is a excerpt from the service documentation:
{
"swagger": "2.0",
"info": {
"description": "Api Documentation",
"version": "1.0",
"title": "Api Documentation",
"termsOfService": "urn:tos",
"contact": {
"name": "Contact Email"
},
"license": {
"name": "Apache 2.0",
"url": "http:\/\/www.apache.org\/licenses\/LICENSE-2.0"
}
},
"host": "localhost:8080",
"basePath": "\/",
"tags": [
{
"name": "current-date-controller",
"description": "Current Date Controller"
},
{
"name": "basic-error-controller",
"description": "Basic Error Controller"
}
]
[..]
}
Swagger UI
Swagger UI allows us to explore our REST API and play around with its services.
We may access it at: http://localhost:8080/swagger-ui.html
Providing Custom API Information
You may override the default API information provided like this:
@Bean
public Docket petApi() {
ApiInfo info = new ApiInfo("My API", "API description", "1.0.0", "http://terms-of-service.url",
"Micha Kops <nospam@hascode.com>", "License", "http://licenseurl");
return new Docket(DocumentationType.SWAGGER_2).apiInfo(info).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build().pathMapping("/")
.directModelSubstitute(LocalDate.class, String.class).genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(newRule(
typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)))
.useDefaultResponseMessages(false)
.globalResponseMessage(RequestMethod.GET,
newArrayList(new ResponseMessageBuilder().code(500).message("500 message")
.responseModel(new ModelRef("Error")).build()))
.securitySchemes(newArrayList(apiKey())).securityContexts(newArrayList(securityContext()));
}
Afterwards our Swagger UI screen looks like this one:
Swagger for JAX-RS based Webservices
For this scenario please feel free to have a look at my blog article: "Documenting RESTful Webservices in Swagger, AsciiDoc and Plain Text with Maven and the JAX-RS Analyzer".
Testing RESTful Webservices
Please feel free to have a look at the following blog articles of mine:
Additional REST articles of mine
Please feel free to have a look at these tutorials of mine covering different aspects of handling or creating RESTful webservices.
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/springboot-swagger-springfox-tutorial.git
Resources
Article Updates
-
2015-08-06: Links to other REST articles of mine added.
-
2015-10-22: Link list updated
-
2015-11-24: Example for providing custom API information added.