0

How do enable Swagger endpoints for my microservice? According to this Baeldung article, it should be super-easy

First, you add a Springfox dependency

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

Second, you include this configuration file

That should be it! No properties, no nothing

@Configuration
public class SpringFoxConfig {                                    
    @Bean
    public Docket api() { 
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}

I did exactly¹ that. But I still get an 404 when trying to hit /v2/api-docs

Why and how do I finally enable those Swagger endpoints?

MRE:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>helloworldMRE</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>helloworldMRE</name>
    <description>helloworldMRE</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
package com.example.helloworldmre;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class HelloWorld {
    private String message = "Hello World!";
}
package com.example.helloworldmre;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
    @GetMapping("/hello-world")
    public HelloWorld getHelloWorld() {
        return new HelloWorld();
    }
}
package com.example.helloworldmre;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SpringFoxConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}
package com.example.helloworldmre;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloworldMreApplication {

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

}

¹ Well, not exactly. The Baeldung article used Boot 2.4.0

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
</dependency>

I use Spring Boot 3

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

When I tried downgrading to 2.4.0, I started to get this exception so I abandoned that idea

java.lang.IllegalArgumentException: Unsupported class file major version 61

2 Answers 2

1

SpringFox doesn't support Springboot v3, the answer is springdoc

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
   <version>2.0.2</version>
</dependency>

This article should help with the conversion

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! Though the real project uses Boot 2 unfortunately
Correct answer (for spring boot 3). Just a hint: current version of springdoc is 2.2.0.
@Kodigas I'm confused, what do you mean by "the real project"? Your question states you are using Spring Boot 3, its just the Baeldung article that is on Spring Boot 2. These are two very different versions of the framework, you can't implement something on one version and expect it to work seamlessly on the other. If you are on Spring Boot 2, use SpringFox, if you're on Spring Boot 3, use SpringDoc; they aren't interchangeable.
0

Alternatively, if you want to stick with spring-fox, you can downgrade to Spring Boot 2. It now works

IMPORTANT! You really need that magic property (see the second to last snippet). More on that here

http://localhost:8080/swagger-ui/index.html works too

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>helloworldservice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>helloworldservice</name>
    <description>helloworldservice</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
package com.example.helloworldmre.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SpringFoxConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}
package com.example.helloworldmre.controller;

import com.example.helloworldmre.data.HelloWorld;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
    @GetMapping("/hello-world")
    public HelloWorld getHelloWorld() {
        return new HelloWorld();
    }
}
package com.example.helloworldmre.data;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class HelloWorld {
    private String message = "Hello World!";
}
package com.example.helloworldmre;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloworldMreApplication {

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

}
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

Response to http://localhost:8080/v2/api-docs:

{
  "swagger": "2.0",
  "info": {
    "description": "Api Documentation",
    "version": "1.0",
    "title": "Api Documentation",
    "termsOfService": "urn:tos",
    "contact": {    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "host": "localhost:8080",
  "basePath": "/",
  "tags": [
    {
      "name": "basic-error-controller",
      "description": "Basic Error Controller"
    },
    {
      "name": "hello-world-controller",
      "description": "Hello World Controller"
    }
  ],
  "paths": {
    "/error": {
      "get": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingGET",
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "head": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingHEAD",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingPOST",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "put": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingPUT",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "delete": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingDELETE",
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "options": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingOPTIONS",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "patch": {
        "tags": [
          "basic-error-controller"
        ],
        "summary": "error",
        "operationId": "errorUsingPATCH",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "object"
              }
            }
          },
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/hello-world": {
      "get": {
        "tags": [
          "hello-world-controller"
        ],
        "summary": "getHelloWorld",
        "operationId": "getHelloWorldUsingGET",
        "produces": [
          "*/*"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/HelloWorld"
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    }
  },
  "definitions": {
    "HelloWorld": {
      "type": "object",
      "properties": {
        "message": {
          "type": "string"
        }
      },
      "title": "HelloWorld"
    },
    "ModelAndView": {
      "type": "object",
      "properties": {
        "empty": {
          "type": "boolean"
        },
        "model": {
          "type": "object"
        },
        "modelMap": {
          "type": "object",
          "additionalProperties": {
            "type": "object"
          }
        },
        "reference": {
          "type": "boolean"
        },
        "status": {
          "type": "string",
          "enum": [
            "ACCEPTED",
            "ALREADY_REPORTED",
            "BAD_GATEWAY",
            "BAD_REQUEST",
            "BANDWIDTH_LIMIT_EXCEEDED",
            "CHECKPOINT",
            "CONFLICT",
            "CONTINUE",
            "CREATED",
            "DESTINATION_LOCKED",
            "EXPECTATION_FAILED",
            "FAILED_DEPENDENCY",
            "FORBIDDEN",
            "FOUND",
            "GATEWAY_TIMEOUT",
            "GONE",
            "HTTP_VERSION_NOT_SUPPORTED",
            "IM_USED",
            "INSUFFICIENT_SPACE_ON_RESOURCE",
            "INSUFFICIENT_STORAGE",
            "INTERNAL_SERVER_ERROR",
            "I_AM_A_TEAPOT",
            "LENGTH_REQUIRED",
            "LOCKED",
            "LOOP_DETECTED",
            "METHOD_FAILURE",
            "METHOD_NOT_ALLOWED",
            "MOVED_PERMANENTLY",
            "MOVED_TEMPORARILY",
            "MULTIPLE_CHOICES",
            "MULTI_STATUS",
            "NETWORK_AUTHENTICATION_REQUIRED",
            "NON_AUTHORITATIVE_INFORMATION",
            "NOT_ACCEPTABLE",
            "NOT_EXTENDED",
            "NOT_FOUND",
            "NOT_IMPLEMENTED",
            "NOT_MODIFIED",
            "NO_CONTENT",
            "OK",
            "PARTIAL_CONTENT",
            "PAYLOAD_TOO_LARGE",
            "PAYMENT_REQUIRED",
            "PERMANENT_REDIRECT",
            "PRECONDITION_FAILED",
            "PRECONDITION_REQUIRED",
            "PROCESSING",
            "PROXY_AUTHENTICATION_REQUIRED",
            "REQUESTED_RANGE_NOT_SATISFIABLE",
            "REQUEST_ENTITY_TOO_LARGE",
            "REQUEST_HEADER_FIELDS_TOO_LARGE",
            "REQUEST_TIMEOUT",
            "REQUEST_URI_TOO_LONG",
            "RESET_CONTENT",
            "SEE_OTHER",
            "SERVICE_UNAVAILABLE",
            "SWITCHING_PROTOCOLS",
            "TEMPORARY_REDIRECT",
            "TOO_EARLY",
            "TOO_MANY_REQUESTS",
            "UNAUTHORIZED",
            "UNAVAILABLE_FOR_LEGAL_REASONS",
            "UNPROCESSABLE_ENTITY",
            "UNSUPPORTED_MEDIA_TYPE",
            "UPGRADE_REQUIRED",
            "URI_TOO_LONG",
            "USE_PROXY",
            "VARIANT_ALSO_NEGOTIATES"
          ]
        },
        "view": {
          "$ref": "#/definitions/View"
        },
        "viewName": {
          "type": "string"
        }
      },
      "title": "ModelAndView"
    },
    "View": {
      "type": "object",
      "properties": {
        "contentType": {
          "type": "string"
        }
      },
      "title": "View"
    }
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.