API명세서는 프론트엔드와 백엔드간의 협업을 위해서 가장 필요한 도구입니다.
백엔드 개발자는 API를 설계하고 공유할때 API명세서를 만듭니다. API 명세서를 다루는 대표적인 툴로는 Swagger가 있습니다.
이번에는 Swagger 을 도입하는 과정과 중간에 겪은 트러블슈팅 해결과정 그리고 더나아가서 직접 github-action으로 배포하는 과정을 공유해보도록하겠습니다.
스프링부트에 스웨거 도입하기#
- Java: 17
- SpringBoot: 3.4.4
build.gradle 에 추가하기#
1
2
3
4
5
6
7
| dependencies {
// Swagger UI
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
...생략...
}
|
application.yaml 에 추가하기#
1
2
3
4
5
6
7
8
9
10
11
| spring:
application:
name: hhplus-concert
...(생략)
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
operationSorter: method
|
도입중 문제 발생#
로컬호스트에서 실행후, 브라우저의 URL에 localhost:8080/swagger-ui/index.html 에 접속했더니 아래이미지와 같은 에러가 발생했습니다.

이 에러에 대한 문제점 원인은 GlobalHandlerException 핸들러의 @RestControllerAdvice 데코레이터를 붙였기 때문에 발생했습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 유효성검사 실패로 발생한 예외처리: 400 에러반환
@ExceptionHandler(InvalidValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleInvalidValidationException(InvalidValidationException e) {
return ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
// 서버내부 오류로 발생한 예외처리: 500 에러반환
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleException(Exception e) {
return ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}
}
|
실행로그에서 발생한 오류문구는 아래와 같이 기재되어있습니다.
이 에러에 대한 해석을 해보자면, “현재 실행중인 코드가 ControllerAdviceBean(Object) 라는 생성자 메서드가 있는줄 알았는데 실제로는 그 메서드가 없는 Spring 라이브러리를 참조하고 있어서 발생하는 오류이기 때문입니다.
java.lang.NoSuchMethodError:
‘void org.springframework.web.method.ControllerAdviceBean.(java.lang.Object)’
Swagger UI는 내부적으로 [GET] /v3/api-docs URL을 호출하는데 이 요청을 처리하는 과정에서 @ControllerAdvice를 스캔하거나 초기화할때 오류가 발생하여 서버 자체에서 500에러를 리턴하여 스웨거문서 로딩에 실패됩니다.
GlobalExceptionHandler에 부여된 @RestControllerAdvice 데코레이터를 제거함으로써 이 문제를 해결했습니다.
로컬호스트에 있는 swagger API명세를 swaggerHub에 옮길 수 없을까?#
1. 로컬호스트의 swagger(localhost:8080/swagger-ui/index.html)에 접속하기#

2. /v3/api-docs에 접속해서 json코드를 얻기#
위의 이미지처럼 작은 글씨로 /v3/api-docs 링크에 들어가게되면 로컬에 띄운 swaggerUI에 대한 JSON 파일을 얻을 수 있습니다. 참고로 pretty 포맷팅보다는 raw 포맷팅으로 json파일을 복사하기를 권장합니다.

3. 코드 포맷 변경 사이트에 들어가서 json 복붙하여 yaml 로 코드를 변환하기#
4. 만들어둔 SwaggerHub 프로젝트에 복붙후 이상없으면 저장하기#
SwaggerHub 프로젝트는 SmartBear의 SwaggerHub에 회원가입 후에 프로젝트를 만들 수 있습니다. 신규계정은 30일동안 유료티어 기능을 사용할 수 있어요~!

github-action을 이용해서 swagger 서버를 배포해보자#
직접 localhost에 있는 스웨거 json코드를 yaml로 변환해서 swaggerHub에 직접 복붙하는 방식은 손이 많이 가기때문에 귀찮고 불편했습니다. API 응답결과나 응답상태값이 추가되거나 변경될 수 있을텐데 유지보수 측면에서는 비효율을 해결하고 싶었습니다.
여러가지 방법들이 존재 했습니다. github-action이 가장쉬운방법이긴한데.. 아쉽게도 github-pages를 이용한 방법을하려면 깃헙계정 1개당 1개의 페이지를 만들 수 있는 제약이 있습니다. 저의 경우에는 이 블로그가 github-page를 이용한 방법이기때문에 다른 방법을 찾아봐야했습니다. 고민하던중 깃헙액션의 워크플로우로 작성하는 방법으로 진행해보기로했습니다.
워크플로우 파일 작성 (1차)#
deploy-openapi-swaggerhub.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| name: Deploy OpenAPI Spec to SwaggerHub
on:
push:
branches: ["main"]
jobs:
deploy-openapi:
runs-on: ubuntu-latest
env:
OWNER: sampleswagger-f17
API_NAME: hh-08-concert
VERSION: 1.0.0
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Give execute permission to gradlew
run: chmod +x ./gradlew
# application 빌드
- name: Build the application
run: ./gradlew bootJar
# application 백그라운드에서 실행
- name: Run the application in background
run: |
java -jar build/libs/concert-0.0.1-SNAPSHOT.jar &
echo $! > pid.txt
# 스프링부트가 완전히 올라올때까지 대기
#
# http://localhost:8080/actuator/health 는 health-check이며
# 서버가 잘 동작하는지 확인할 수 있습니다.
- name: Wait for server to be ready
run: |
for i in {1..30}; do
curl --silent http://localhost:8080/actuator/health && exit 0
echo "⏳ Waiting for server ..."
sleep 2
done
echo "❌ Server did not start in time"
cat pid.txt | xargs kill
exit 1
# 로컬호스트내 OpenAPI 스펙다운로드
- name: Download OpenAPI spec
run: curl http://localhost:8080/v3/api-docs -o openapi.json
- name: Install SwaggerHub CLI
run: npm install -g swaggerhub-cli
- name: Push to swaggerHub
run: |
swaggerhub api:push <OWNER>/<API_NAME>/<VERSION> \
--file openapi.json \
--token ${{ secrets.SWAGGERHUB_API_KEY }} \
--visibility public
|
swaggerHub의 정보 파악하기#
스웨거헙은 OWNER / API_NAME / VERSION 으로 구성되어있습니다.
아래의 이미지의 URL을 확인해보시면 쉽게 이해할 수 있습니다.

스웨거 API키 발급과 깃헙시크릿키 값에 넣기#
swaggerHub의 API 시크릿키 찾기

swaggerHub API 시크릿값을 Github 래포지토리 시크릿값에 넣기

반복되는 빌드 실패…#

결국은 워크플로우를 했지만 실패가 나왔습니다. 그에대한 이유는 아무래도 스프링부트를 실행하려면 데이터베이스와의 연결이 필요하기 때문입니다. 이 프로세스가 되려면 원격으로 데이터베이스가 필요합니다. 하지만 스웨거만을 배포해야되므로 굳이 클라우드의 DB를 붙일 필요가 없으므로 TestContainers를 하기로 했습니다.
TestContainer 설정하기#
이 프로젝트에서는 Mysql을 사용하기 때문에 TestContainer도 동일한 데이터베이스를 사용해야합니다. build.gradle에 TestContainer 관련해서 추가해야합니다.
1
2
3
4
5
6
| // build.gradle
dependencies {
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:mysql'
}
|
워크플로우파일 작성 (2차)#
application-ci.yml
깃헙액션에서 테스트컨테이너를 실행할때 사용하는 설정값입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always
server:
port: 8080
spring:
dataSource:
url: jdbc:tc:mysql:8.0:///testdb
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
username: test
password: test
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibername.dialect.MySQL8Dialect
logging:
level:
root: DEBUG
|
deploy-openapi-swaggerhub.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
| name: Deploy OpenAPI Spec to SwaggerHub
on:
push:
branches: ["main", "step05", "step06"]
jobs:
deploy-openapi:
runs-on: ubuntu-latest
env:
OWNER: sampleswagger-f17
API_NAME: hh-08-concert
VERSION: 1.0.0
JAVA_TOOL_OPTIONS: "-Dorg.slf4j.simpleLogger.defaultLogLevel=debug"
steps:
- name: Checkout
uses: actions/checkout@v3
# Java 설치
- name: Set up java
uses: actions/setup-java@v3
with:
java-version: "17"
distribution: "temurin"
- name: Give execute permission to gradlew
run: chmod +x ./gradlew
# application 빌드
- name: Build the application
run: ./gradlew bootJar
# application 백그라운드에서 실행
- name: Run the application in background
run: |
java -jar build/libs/concert-0.0.1-SNAPSHOT.jar --spring.profiles.active=ci &
echo $! > pid.txt
# TestContainer 로그 확인 추가
- name: Debug logs if server fails
if: failure()
run: |
echo "🧩 Showing last 100 lines from app logs (if any)"
tail -n 100 build/libs/*.log || echo "No log file"
echo "🐳 Running Docker containers"
docker ps -a || echo "Docker info not available"
# 서버가 통신되는지 확인
- name: Wait for server to be ready
run: |
for i in {1..30}; do
curl --silent http://localhost:8080/actuator/health && exit 0
echo "⏳ Waiting for server ..."
sleep 2
done
echo "❌ Server did not start in time"
echo "📜 ==== APP LOG START ===="
cat app.log || echo "No log file found"
echo "📜 ==== APP LOG END ===="
cat pid.txt | xargs kill
exit 1
# 로컬호스트내 OpenAPI 스펙다운로드
- name: Download OpenAPI spec
run: curl http://localhost:8080/v3/api-docs -o openapi.json
- name: Install SwaggerHub CLI
run: npm install -g swaggerhub-cli
- name: Push to swaggerHub
run: |
swaggerhub api:push $OWNER/$API_NAME/$VERSION \
--file openapi.json \
--token ${{ secrets.SWAGGERHUB_API_KEY }} \
--visibility public
|
하지만 아쉽게도 Wait for server to be ready 단계에서 계속 똑같은 실패에서 발생을합니다. 연결이 되지 않았기 때문인데요. ( 이 문제에 대한 자세한 솔루션을 찾은경우에는 dmsrkd1216@gmail.com 으로 이메일 보내주시면 감사하겠습니다! )
지금 과제를 해야되는데 과제와 상관없는 삽질을 해버리고말았네요 ㅠㅠ 시간이 많이 소요된 관계로 실패로 끝났습니다. 약 3시간 39분정도 시도를 해봤습니다. 결과를 못이뤄낸건 아쉽지만 그래도 후회는 없습니다.
일단은 불편하더라도 직접 제가 복붙해서 사용하는 방안으로 진행해야될거같습니다.
아무래도 테스트컨테이너가 잘 지원한다해도 워크플로우에서의 서버와의 통신이 어려울거같습니다.
만일 성공이 됐다하더라도, 추후에 레디스나, kafka 이 추가된다면 이 워크플로우에도 연결을 해서 반영을 해야될겁니다. TestContainers처럼 워크플로우 실행중에서만 사용되는 소프트웨어가 있는지 직접 찾아봐야될거 같습니다.