DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Functional Endpoints: Alternative to Controllers in WebFlux
  • How Spring Boot Starters Integrate With Your Project
  • Minimizing Latency in Kafka Streaming Applications That Use External API or Database Calls
  • Integrate Spring With Open AI

Trending

  • Docker Model Runner: Streamlining AI Deployment for Developers
  • A Modern Stack for Building Scalable Systems
  • Kubeflow: Driving Scalable and Intelligent Machine Learning Systems
  • Revolutionizing Financial Monitoring: Building a Team Dashboard With OpenObserve
  1. DZone
  2. Data Engineering
  3. Databases
  4. Documenting Your Spring API with Swagger

Documenting Your Spring API with Swagger

By 
Matt Raible user avatar
Matt Raible
·
Mar. 27, 14 · Interview
Likes (5)
Comment
Save
Tweet
Share
119.6K Views

Join the DZone community and get the full member experience.

Join For Free

over the last several months, i've been developing a rest api using spring boot . my client hired an outside company to develop a native ios app, and my development team was responsible for developing its api. our main task involved integrating with epic , a popular software system used in health care. we also developed a crowd -backed authentication system, based loosely on philip sorst's angular rest security .

to document our api, we used spring mvc integration for swagger (a.k.a. swagger-springmvc). i briefly looked into swagger4spring-web , but gave up quickly when it didn't recognize spring's @restcontroller. we started with swagger-springmvc 0.6.5 and found it fairly easy to integrate. unfortunately, it didn't allow us to annotate our model objects and tell clients which fields were required. we were quite pleased when a new version (0.8.2) was released that supports swagger 1.3 and its @apimodelproperty.

what is swagger?
the goal of swagger is to define a standard, language-agnostic interface to rest apis which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection.

to demonstrate how swagger works, i integrated it into josh long's x-auth-security project. if you have a boot-powered project, you should be able to use the same steps.

1. add swagger-springmvc dependency to your project.

<dependency>
    <groupid>com.mangofactory</groupid>
    <artifactid>swagger-springmvc</artifactid>
    <version>0.8.2</version>
</dependency>

note: on my client's project, we had to exclude "org.slf4j:slf4j-log4j12" and add "jackson-module-scala_2.10:2.3.1" as a dependency. i did not need to do either of these in this project.

2. add a swaggerconfig class to configure swagger.

the swagger-springmvc documentation has an example of this with a bit more xml.

package example.config;

import com.mangofactory.swagger.configuration.jacksonscalasupport;
import com.mangofactory.swagger.configuration.springswaggerconfig;
import com.mangofactory.swagger.configuration.springswaggermodelconfig;
import com.mangofactory.swagger.configuration.swaggerglobalsettings;
import com.mangofactory.swagger.core.defaultswaggerpathprovider;
import com.mangofactory.swagger.core.swaggerapiresourcelisting;
import com.mangofactory.swagger.core.swaggerpathprovider;
import com.mangofactory.swagger.scanners.apilistingreferencescanner;
import com.wordnik.swagger.model.*;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.value;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.componentscan;
import org.springframework.context.annotation.configuration;

import java.util.arraylist;
import java.util.arrays;
import java.util.list;

import static com.google.common.collect.lists.newarraylist;

@configuration
@componentscan(basepackages = "com.mangofactory.swagger")
public class swaggerconfig {

    public static final list<string> default_include_patterns = arrays.aslist("/news/.*");
    public static final string swagger_group = "mobile-api";

    @value("${app.docs}")
    private string docslocation;

    @autowired
    private springswaggerconfig springswaggerconfig;
    @autowired
    private springswaggermodelconfig springswaggermodelconfig;

    /**
     * adds the jackson scala module to the mappingjackson2httpmessageconverter registered with spring
     * swagger core models are scala so we need to be able to convert to json
     * also registers some custom serializers needed to transform swagger models to swagger-ui required json format
     */
    @bean
    public jacksonscalasupport jacksonscalasupport() {
        jacksonscalasupport jacksonscalasupport = new jacksonscalasupport();
        //set to false to disable
        jacksonscalasupport.setregisterscalamodule(true);
        return jacksonscalasupport;
    }


    /**
     * global swagger settings
     */
    @bean
    public swaggerglobalsettings swaggerglobalsettings() {
        swaggerglobalsettings swaggerglobalsettings = new swaggerglobalsettings();
        swaggerglobalsettings.setglobalresponsemessages(springswaggerconfig.defaultresponsemessages());
        swaggerglobalsettings.setignorableparametertypes(springswaggerconfig.defaultignorableparametertypes());
        swaggerglobalsettings.setparameterdatatypes(springswaggermodelconfig.defaultparameterdatatypes());
        return swaggerglobalsettings;
    }

    /**
     * api info as it appears on the swagger-ui page
     */
    private apiinfo apiinfo() {
        apiinfo apiinfo = new apiinfo(
                "news api",
                "mobile applications and beyond!",
                "https://helloreverb.com/terms/",
                "matt@raibledesigns.com",
                "apache 2.0",
                "http://www.apache.org/licenses/license-2.0.html"
        );
        return apiinfo;
    }

    /**
     * configure a swaggerapiresourcelisting for each swagger instance within your app. e.g. 1. private  2. external apis
     * required to be a spring bean as spring will call the postconstruct method to bootstrap swagger scanning.
     *
     * @return
     */
    @bean
    public swaggerapiresourcelisting swaggerapiresourcelisting() {
        //the group name is important and should match the group set on apilistingreferencescanner
        //note that swaggercache() is by defaultswaggercontroller to serve the swagger json
        swaggerapiresourcelisting swaggerapiresourcelisting = new swaggerapiresourcelisting(springswaggerconfig.swaggercache(), swagger_group);

        //set the required swagger settings
        swaggerapiresourcelisting.setswaggerglobalsettings(swaggerglobalsettings());

        //use a custom path provider or springswaggerconfig.defaultswaggerpathprovider()
        swaggerapiresourcelisting.setswaggerpathprovider(apipathprovider());

        //supply the api info as it should appear on swagger-ui web page
        swaggerapiresourcelisting.setapiinfo(apiinfo());

        //global authorization - see the swagger documentation
        swaggerapiresourcelisting.setauthorizationtypes(authorizationtypes());

        //every swaggerapiresourcelisting needs an apilistingreferencescanner to scan the spring request mappings
        swaggerapiresourcelisting.setapilistingreferencescanner(apilistingreferencescanner());
        return swaggerapiresourcelisting;
    }

    @bean
    /**
     * the apilistingreferencescanner does most of the work.
     * scans the appropriate spring requestmappinghandlermappings
     * applies the correct absolute paths to the generated swagger resources
     */
    public apilistingreferencescanner apilistingreferencescanner() {
        apilistingreferencescanner apilistingreferencescanner = new apilistingreferencescanner();

        //picks up all of the registered spring requestmappinghandlermappings for scanning
        apilistingreferencescanner.setrequestmappinghandlermapping(springswaggerconfig.swaggerrequestmappinghandlermappings());

        //excludes any controllers with the supplied annotations
        apilistingreferencescanner.setexcludeannotations(springswaggerconfig.defaultexcludeannotations());

        //
        apilistingreferencescanner.setresourcegroupingstrategy(springswaggerconfig.defaultresourcegroupingstrategy());

        //path provider used to generate the appropriate uri's
        apilistingreferencescanner.setswaggerpathprovider(apipathprovider());

        //must match the swagger group set on the swaggerapiresourcelisting
        apilistingreferencescanner.setswaggergroup(swagger_group);

        //only include paths that match the supplied regular expressions
        apilistingreferencescanner.setincludepatterns(default_include_patterns);

        return apilistingreferencescanner;
    }

    /**
     * example of a custom path provider
     */
    @bean
    public apipathprovider apipathprovider() {
        apipathprovider apipathprovider = new apipathprovider(docslocation);
        apipathprovider.setdefaultswaggerpathprovider(springswaggerconfig.defaultswaggerpathprovider());
        return apipathprovider;
    }


    private list<authorizationtype> authorizationtypes() {
        arraylist<authorizationtype> authorizationtypes = new arraylist<>();

        list<authorizationscope> authorizationscopelist = newarraylist();
        authorizationscopelist.add(new authorizationscope("global", "access all"));

        list<granttype> granttypes = newarraylist();

        loginendpoint loginendpoint = new loginendpoint(apipathprovider().getappbasepath() + "/user/authenticate");
        granttypes.add(new implicitgrant(loginendpoint, "access_token"));

        return authorizationtypes;
    }

    @bean
    public swaggerpathprovider relativeswaggerpathprovider() {
        return new apirelativeswaggerpathprovider();
    }

    private class apirelativeswaggerpathprovider extends defaultswaggerpathprovider {
        @override
        public string getappbasepath() {
            return "/";
        }

        @override
        public string getswaggerdocumentationbasepath() {
            return "/api-docs";
        }
    }
}

the apipathprovider class referenced above is as follows:

package example.config;

import com.mangofactory.swagger.core.swaggerpathprovider;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.util.uricomponentsbuilder;

import javax.servlet.servletcontext;

public class apipathprovider implements swaggerpathprovider {
    private swaggerpathprovider defaultswaggerpathprovider;
    @autowired
    private servletcontext servletcontext;

    private string docslocation;

    public apipathprovider(string docslocation) {
        this.docslocation = docslocation;
    }

    @override
    public string getapiresourceprefix() {
        return defaultswaggerpathprovider.getapiresourceprefix();
    }

    public string getappbasepath() {
        return uricomponentsbuilder
                .fromhttpurl(docslocation)
                .path(servletcontext.getcontextpath())
                .build()
                .tostring();
    }

    @override
    public string getswaggerdocumentationbasepath() {
        return uricomponentsbuilder
                .fromhttpurl(getappbasepath())
                .pathsegment("api-docs/")
                .build()
                .tostring();
    }

    @override
    public string getrequestmappingendpoint(string requestmappingpattern) {
        return defaultswaggerpathprovider.getrequestmappingendpoint(requestmappingpattern);
    }

    public void setdefaultswaggerpathprovider(swaggerpathprovider defaultswaggerpathprovider) {
        this.defaultswaggerpathprovider = defaultswaggerpathprovider;
    }
}

in src/main/resources/application.properties , add an "app.docs" property. this will need to be changed as you move your application from local -> test -> staging -> production. spring boot's externalized configuration makes this fairly simple.

app.docs=http://localhost:8080

3. verify swagger produces json.

after completing the above steps, you should be able to see the json swagger generates for your api. open http://localhost:8080/api-docs in your browser or curl http://localhost:8080/api-docs .

{
    "apiversion": "1",
    "swaggerversion": "1.2",
    "apis": [
        {
            "path": "http://localhost:8080/api-docs/mobile-api/example_newscontroller",
            "description": "example.newscontroller"
        }
    ],
    "info": {
        "title": "news api",
        "description": "mobile applications and beyond!",
        "termsofserviceurl": "https://helloreverb.com/terms/",
        "contact": "matt@raibledesigns.com",
        "license": "apache 2.0",
        "licenseurl": "http://www.apache.org/licenses/license-2.0.html"
    }
}

4. copy swagger ui into your project.

swagger ui is a good-looking javascript client for swagger's json. i integrated it using the following steps:

git clone https://github.com/wordnik/swagger-ui
cp -r swagger-ui/dist ~/dev/x-auth-security/src/main/resources/public/docs

i modified docs/index.html, deleting its header (<div id='header'>) element, as well as made its url dynamic.

...
$(function () {
  var apiurl = window.location.protocol + "//" + window.location.host;
  if (window.location.pathname.indexof('/api') > 0) {
    apiurl += window.location.pathname.substring(0, window.location.pathname.indexof('/api'))
  }
  apiurl += "/api-docs";
  log('api url: ' + apiurl);
  window.swaggerui = new swaggerui({
    url: apiurl,
    dom_id: "swagger-ui-container",
...

after making these changes, i was able to open fire up the app with "mvn spring-boot:run" and view http://localhost:8080/docs/index.html in my browser.

5. annotate your api.

there are two services in x-auth-security: one for authentication and one for news. to provide more information to the "news" service's documentation, add @api and @apioperation annotations. these annotations aren't necessary to get a service to show up in swagger ui, but if you don't specify the @api("user"), you'll end up with an ugly-looking class name instead (e.g. example_xauth_userxauthtokencontroller).

@restcontroller
@api(value = "news", description = "news api")
class newscontroller {

    map<long, newsentry> entries = new concurrenthashmap<long, newsentry>();

    @requestmapping(value = "/news", method = requestmethod.get)
    @apioperation(value = "get news", notes = "returns news items")
    collection<newsentry> entries() {
        return this.entries.values();
    }

    @requestmapping(value = "/news/{id}", method = requestmethod.delete)
    @apioperation(value = "delete news item", notes = "deletes news item by id")
    newsentry remove(@pathvariable long id) {
        return this.entries.remove(id);
    }

    @requestmapping(value = "/news/{id}", method = requestmethod.get)
    @apioperation(value = "get a news item", notes = "returns a news item")
    newsentry entry(@pathvariable long id) {
        return this.entries.get(id);
    }

    @requestmapping(value = "/news/{id}", method = requestmethod.post)
    @apioperation(value = "update news", notes = "updates a news item")
    newsentry update(@requestbody newsentry news) {
        this.entries.put(news.getid(), news);
        return news;
    }
...
}

you might notice the screenshot above only shows news. this is because swaggerconfig.default_include_patterns only specifies news. the following will include all apis.

public static final list<string> default_include_patterns = arrays.aslist("/.*");

after adding these annotations and modifying swaggerconfig , you should see all available services.

in swagger-springmvc 0.8.x, the ability to use @apimodel and @apimodelproperty annotations was added. this means you can annotate newsentry to specify which fields are required.

@apimodel("news entry")
public static class newsentry {
    @apimodelproperty(value = "the id of the item", required = true)
    private long id;
    @apimodelproperty(value = "content", required = true)
    private string content;

    // getters and setters
}

this results in the model's documentation showing up in swagger ui. if "required" isn't specified, a property shows up as optional .

parting thoughts

the qa engineers and 3rd party ios developers have been very pleased with our api documentation. i believe this is largely due to swagger and its nice-looking ui. the swagger ui also provides an interface to test the endpoints by entering parameters (or json) into html forms and clicking buttons. this could benefit those qa folks that prefer using selenium to test html (vs. raw rest endpoints).

i've been quite pleased with swagger-springmvc, so kudos to its developers. they've been very responsive in fixing issues i've reported . the only thing i'd like is support for recognizing jsr303 annotations (e.g. @notnull) as required fields.

to see everything running locally, checkout my modified x-auth-security project on github and the associated commits for this article.

Spring Framework API

Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Functional Endpoints: Alternative to Controllers in WebFlux
  • How Spring Boot Starters Integrate With Your Project
  • Minimizing Latency in Kafka Streaming Applications That Use External API or Database Calls
  • Integrate Spring With Open AI

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!