초기의 웹 페이지는 이미 만들어진 HTML 파일을 그대로 내려주는 방식이었다. 이런 방식은 모든 사용자에게 같은 화면을 보여주기에는 충분하지만, 요청마다 다른 결과를 만들어야 하는 기능에는 한계가 있다.
예를 들어 영화 추천 기능을 만든다고 하면, 서버는 단순히 고정된 문서를 보내는 것이 아니라 요청이 들어온 순간 Java코드를 실행해 영화 목록 중 하나를 선택하고, 그 결과를 클라이언트에게 응답해야 한다.
즉, API 서버는 단순한 파일 전달자가 아니라 요청을 해석하고, 필요한 로직을 실행하고, 응답을 만들어내는 프로그램이어야 한다. Servlet은 이 흐름을 직접 확인하기 좋은 가장 기본적인 Java 웹 기술이다.
Servlet은 클라이언트의 HTTP 요청을 처리하고 응답을 만들어내는 Java 클래스다. 하지만 Servlet 클래스 혼자서는 실행될 수 없다. 요청을 받고, 알맞은 Servlet을 찾아 실행하고, 응답을 다시 클라이언트에게 보내주는 실행 환경이 필요하다. 이 역할을 하는 것이 Servlet Container이며, 대표적으로 Tomcat이 있다.
식당으로 비유하면 다음과 같다.
| 웹 개념 | 비유 | 역할 |
|---|---|---|
| Client / Postman | 손님 | 서버에 요청을 보냄 |
| URL | 주문 메뉴 이름 | 어떤 기능을 실행할지 지정 |
| Servlet Container | 매장 관리자 | 요청을 받아 담당 Servlet에게 전달 |
| Servlet | 요리 담당자 | 실제 로직을 실행하고 결과를 만듦 |
| Request | 주문서 | 클라이언트가 보낸 요청 정보 |
| Response | 완성된 음식 | 서버가 클라이언트에게 보내는 응답 |

클라이언트가 /hello 요청을 보내면 Web Application Server 내부에서 Request가 생성되고, Servlet이 실행된 뒤 Response를 통해 응답이 반환되는 흐름을 보여준다.
/hello API 구현으로 확인한 기본 요청-응답 구조첫번째 API는 /hello 요청을 받으면 hello 문자열을 응답하는 단순한 Servlet API이다.
핵심 코드는 다음 구조로 동작한다.
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain; charset=UTF-8");
resp.getWriter().print("hello");
}
}
여기서 중요한 점은 hello를 Java 메서드의 반환값처럼 return "hello"로 돌려주는 것이 아니라는 점이다. Servlet에서는 HTTPServletResponse 객체에 직접 응답 내용을 작성한다.
resp.getWriter().print("hello");
즉, hello는 메서드의 return 값이 아니라 HTTP 응답 body에 작성된 문자열이다.
또한 Spring Boot에서 @WebServlet이 붙은 Servlet을 인식하게 하려면 메인 애플리케이션 클래스에 @ServletComponentScan을 추가해야 한다.
@ServletComponentScan
@SpringBootApplication
public class SpringPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPracticeApplication.class, args);
}
}
HelloServlet 구현 코드, @ServletComponentScan 설정, Spring Boot 내장 Tomcat 서버 실행 로그를 한 화면에서 확인한 이미지다.
실습 중 "연결이 안 된 것 같다"는 의문이 있었다. 이때 확인해야 할 기준은 두 가지로 나뉜다.
| 확인 대상 | 의미 |
|---|---|
| IntelliJ 실행 로그 | 서버가 켜졌는지 확인 |
| Postman 응답 결과 | API가 실제 요청을 처리했는지 확인 |
실행 로그에 다음과 같은 문장이 보이면 서버는 정상적으로 실행된 것이다.
Tomcat started on port 8080
Started SpringPracticeApplication
하지만 이것만으로 API 성공을 판단할 수는 없다. 실제 성공 여부는 Postman에서 요청을 보내고 200 OK 응답 body를 확인해야 한다.
GET http://localhost:8080/hello 요청 결과는 다음과 같았다.
Status: 200 OK
Body: hello
Postman에서 GET /hello 요청을 보낸 결과 200 OK와 hello 응답을 확인한 화면이다.
HelloServlet을 만든 뒤 같은 프로젝트와 같은 기본 패키지 안에 MovieRecommendationServlet을 추가했다. 이때 기존 HelloServlet이 더 이상 작동하지 않는지 의문이 생겼다.
결론은 문제 없이 함께 동작한다였다. 이유는 두 Servlet의 URL 매핑이 다르기 때문이다.
| 요청 URL | 처리하는 Servlet | 응답 |
|---|---|---|
/hello | HelloServlet | hello |
/movies/recommendation | MovieRecommendationServlet | 영화 추천 JSON |
Servlet Container는 클래스가 여러 개 있더라도 요청 URL을 기준으로 어떤 Servlet을 실행할지 결정한다. 따라서 URL 매핑이 다르면 하나의 애플리케이션 안에서 여러 API가 동시에 존재할 수 있다.
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
}
@WebServlet("/movies/recommendation")
public class MovieRecommendationServlet extends HttpServlet {
}
중요한 기준은 클래스가 같은 패키지에 있는지가 아니라, 각 Servlet이 어떤 URL과 연결되어 있는지이다.
두번째 API는 /movies/recommendation 요청이 들어오면 영화 목록 중 하나를 랜덤으로 선택하고, JSON 형태로 응답하는 기능이다.
핵심 흐름은 다음과 같다.
GET /movies/recommendation 요청
→ MovieRecommendationServlet 실행
→ 영화 목록에서 랜덤으로 하나 선택
→ Map에 title이라는 key로 영화 제목 저장
→ ObjectMapper로 JSON 문자열 변환
→ Response body에 JSON 작성
핵심 코드는 다음과 같은 흐름을 가진다.
int randomIndex = (int) (Math.random() * recommendedMovies.size());
String randomMovie = recommendedMovies.get(randomIndex);
Map<String, String> movieMap = Map.of("title", randomMovie);
response.setContentType("application/json; charset=UTF-8");
String json = objectMapper.writeValueAsString(movieMap);
response.getWriter().print(json);
여기서 Map은 JSON이 아니다. Map은 Java 안에서 사용하는 key-value 자료구조이고, ObjectMapper가 이 Map을 JSON 문자열로 변환한다.
| 단계 | 형태 |
|---|---|
Map.of("title", randomMovie) | Java 자료구조 |
objectMapper.writeValueAsString(movieMap) | JSON 문자열 |
response.getWriter().print(json) | HTTP 응답 body 작성 |
또한 JSON 응답을 보낼 때는 응답 형식을 명확히 알려주어야 한다.
response.setContentType("application/json; charset=UTF-8");
/hello API에서는 일반 텍스트였기 때문에 text/plain을 사용했고, 영화 추천 API에서는 JSON을 보내기 때문에 application/json을 사용했다.
| API | Content-Type | 응답 예시 |
|---|---|---|
/hello | text/plain | hello |
/movies/recommendation | application/json | { "title": "쇼탱크 탈출" } |
MovieRecommendationServlet을 구현하고, 같은 애플리케이션 안에서 /movies/recommendation API를 추가한 화면이다.
Postman에서 GET http://localhost:8080/movies/recommendation 요청을 보낸 결과, 다음과 같은 JSON 응답을 확인했다.
{
"title": "쇼생크 탈출"
}
또 한 번 요청을 보내면 다음과 같이 다른 영화가 반환되기도 했다.
{
"title": "대부"
}
또 다른 요청에서는 다음과 같은 결과도 확인했다.
{
"title": "다크 나이트"
}
이 결과는 같은 URL을 호출해도 서버 내부의 Java 로직이 매번 다시 실행된다는 것을 보여준다. Postman의 Send 버튼을 누를 때마다 새로운 HTTP 요청이 발생하고, 그때마다 doGet() 메서드가 다시 실행된다.
다만 랜덤이기 때문에 매번 반드시 다른 영화가 나오는 것은 아니다. 같은 영화가 연속으로 나와도 정상이다.
GET /movies/recommendation 요청 결과 200 OK와 함께 { "title": "쇼생크 탈출" } JSON 응답을 확인한 화면이다.
같은 URL로 여러 번 요청을 보냈을 때 영화 제목이 달라지는 것을 확인한 화면이다. 이를 통해 응답이 고정된 파일이 아니라 서버 로직에 의해 동적으로 생성된다는 점을 확인할 수 있다.
hello는 단순 텍스트 응답이고, 영화 추천 결과는 JSON 응답이다. 이 차이는 단순히 겉모양만 다른 것이 아니라 클라이언트가 데이터를 해석하는 방식에도 영향을 준다.
hello
{
"title": "대부"
}
실제 API에서는 JSON 형식이 더 많이 사용된다. 프론트엔드나 다른 클라이언트가 key를 기준으로 필요한 값을 꺼내기 쉽기 때문이다.
Servlet 실습은 Spring MVC로 넘어가기 전의 기본 구조를 이해하는 데 의미가 있다. 나중에 @RestController, @GetMapping, ResponseEntity를 사용하면 지금보다 코드가 훨씬 짧아진다.
하지만 내부 흐름은 크게 다르지 않다.
요청 URL 확인
→ 알맞은 처리 코드 실행
→ 응답 데이터 생성
→ 클라이언트에게 반환
Servlet에서는 HttpServletResponse에 직접 응답을 작성했다. 반면 Spring Controller에서는 메서드의 반환값을 Spring이 HTTP 응답으로 변환해준다.
| 구분 | Servlet 방식 | Spring Controller 방식 |
|---|---|---|
| URL 매핑 | @WebServlet | @GetMapping |
| 요청 처리 메서드 | doGet() | 일반 메서드 |
| 응답 작성 | response.getWriter().print() | return |
| JSON 변환 | 직접 ObjectMapper 사용 | Spring이 자동 처리 |
따라서 이번 Servlet 실습은 이후 Spring Controller를 이해하기 위한 기초가 된다. Spring이 편하게 감싸주기 전에는 요청과 응답을 개발자가 직접 다뤄야 한다는 점을 확인할 수 있었다.
Servlet 기반 API 구현을 통해 클라이언트 요청이 서버에 도착한 뒤 어떤 Servlet이 실행되고, 응답이 어떻게 만들어지는지 확인했다. /hello API에서는 단순 문자열 응답을 확인했고, /movies/recommendation API에서는 Java의 List, Map, ObjectMapper를 활용해 JSON 응답을 생성했다.
또한 같은 프로젝트와 같은 기본 패키지 안에 여러 Servlet 클래스를 만들어도 URL 매핑이 다르면 각각 독립적으로 동작한다는 점을 확인했다. /hello는 HelloServlet, /movies/recommendation은 MovieRecommendationServlet이 처리하며, 기존 API가 새 API 때문에 사라지는 것이 아니라는 점도 실습으로 이해했다.
이번 학습의 핵심은 Servlet 코드를 외우는 것이 아니라, 요청 → URL 매핑 → Servlet 실행 → Response 작성 → Postman 확인이라는 흐름을 이해하는 것이었다.