From an empty folder to a running endpoint
Generate TaskFlow, inspect the project skeleton, run the app, add a first REST endpoint, and verify the full generate-run-hit loop.
Assumes you know
After this you should be able to
- Run a Spring Boot app
- Build and call a REST endpoint
Spring Boot by Building · Chapter 2
We generate the TaskFlow project — the one app we grow through the whole course — open every file the generator made, understand why each exists, then write one controller and watch a URL answer in the browser. Nothing is skipped and nothing is magic by the end.
Choose your route#
Study the same From an empty folder to a running endpoint explanation with a different purpose: build and verify it in Learn deeply, or retrieve and defend it in Prepare for interviews.
- Run a Spring Boot app
- Build and call a REST endpoint
Shared core#
The chapter below remains the canonical explanation for both goals. Keep its model, examples, and trade-offs shared; only the deliberate practice changes with the route.
Learn deeply: build evidence#
Use this chapter’s TaskFlow work to make one small, observable change. Run the stated acceptance checks before recording practice.
Prepare for interviews: explain the trade-off#
Without notes, explain the boundary this chapter protects, one concrete implementation decision, and the cost of a tempting shortcut. Then answer one follow-up using the chapter’s own example.
By the last step you will have a Spring Boot app running on your machine, and hitting
http://localhost:8080/ping will return pong. Small on purpose — the point is to make
the full loop (generate → understand → run → hit) feel routine, so later chapters can add layers
without the setup ever being the scary part.
A JDK 17 or newer (check with java -version) and any editor — IntelliJ
IDEA Community is free and the smoothest for Spring. You do not need Maven installed: the
generated project ships with a wrapper (mvnw) that fetches the right version itself. No database to
install — we don't touch one until Chapter 4.
Section 1Generate the project with Spring Initializr#
start.spring.io is a web form that hands
you a ready-made project skeleton as a .zip. Every choice below maps to something you now understand
from Chapter 1.
- Open start.spring.io and set the top-left options
Project: Maven. Language: Java. Spring Boot: pick the current stable release without SNAPSHOT next to it (a snapshot is a preview build). These notes work with modern Boot 4.x; if you follow an older Boot 3.x tutorial, the core annotations here are the same.
- Fill in Project Metadata
Group:
com.example. Artifact:taskflow. This becomes your base packagecom.example.taskflow— the root of the folder map from Chapter 1. Packaging: Jar. Java: 17 (or your installed version). - Add exactly one dependency: Spring Web
Click Add Dependencies → search Spring Web → add it. That single starter pulls in Spring MVC and an embedded Tomcat server, which is why your app can serve HTTP without you installing a server. We add more starters in later chapters — one per new capability.
- Generate, then unzip
Click Generate. A
taskflow.zipdownloads. Unzip it somewhere sensible and open thetaskflowfolder in your IDE. IntelliJ will say "Maven project detected" — let it import; it downloads dependencies once (give it a minute).
A starter like spring-boot-starter-web is a meal kit: one line in your build file pulls
a curated, version-matched set of libraries that all work together. Without starters you'd hand-pick a dozen
libraries and pray their versions agree. That "dependency hell" is exactly what Boot removed.
Section 2Meet every file the generator made#
Open the project. It looks like this. Nothing here is optional decoration — let's name each piece.
The two files that matter today#
package com.example.taskflow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TaskflowApplication {
public static void main(String[] args) {
SpringApplication.run(TaskflowApplication.class, args);
}
}main()is a plain Java entry point — the same one you've written since day one. Running it starts everything.SpringApplication.run(...)boots the assembly line: it starts the embedded Tomcat server and scans your packages for classes tagged@RestController,@Service, etc., creating a bean for each.@SpringBootApplicationis three annotations in one; the important part is component scanning starts from this class's package — which is why every other class you write must live undercom.example.taskflowto be found.
If you put a controller in a package outside com.example.taskflow (say
com.other), Spring's scan never sees it, no bean is made, and your URL returns 404 with no error.
Rule: keep every class under the application's package. This is the "why isn't my endpoint
working" answer 80% of the time.
The pom.xml is the build file. You rarely edit it by hand except to add dependencies — here's the
one line that came from choosing Spring Web:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>Section 3Run it once — before writing anything#
Run the app now, empty. This proves the setup works and teaches you what a healthy startup looks like, so a broken one is obvious later.
In the IDE: open TaskflowApplication.java and click the green ▶ next to main(). Or from
the project root in a terminal:
Now visit http://localhost:8080 in a browser. You'll get a Whitelabel Error Page
saying 404. That is success. The server is running; you simply haven't told it about any URL yet. Stop
the app (red ■, or Ctrl-C) and let's give it one.
Two lines are your health check: Tomcat started on port 8080 (the server is up) and
Started TaskflowApplication (beans wired, no errors). If you don't see both, scroll up — the first
red ERROR/Caused by: line names the problem, and it's almost always a port clash or a
typo'd annotation, not something mysterious.
Section 4Write your first controller#
Time for a real endpoint. Create a controller package and a class inside it. In IntelliJ:
right-click the com.example.taskflow package → New → Package → name it controller, then
add a class PingController.
package com.example.taskflow.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PingController {
@GetMapping("/ping")
public String ping() {
return "pong";
}
}package ...controller;— lives under the app package, so component scan finds it. (Remember pitfall #1.)@RestController— the name tag from Chapter 1. Spring makes one bean of this class and knows its methods return data straight into the HTTP response body (as text/JSON), not a web-page name.@GetMapping("/ping")— "when an HTTP GET arrives at/ping, run this method."GETis what a browser does when you type a URL.return "pong";— the returned String becomes the response body. That's the whole endpoint.
Run the app again (▶) and visit http://localhost:8080/ping:
That round trip — browser → Tomcat → your @GetMapping method → back — is the top rung of Fig 1 from
Chapter 1. Everything we add from here just extends the same trip downward.
No new PingController(). No server-startup code. No "register this URL" wiring. You wrote a tagged
class and a tagged method; Spring's component scan found them at boot and connected the URL for you. That is
IoC doing its job — and it's exactly the mechanism that will inject a Service into this controller next
chapter.
Section 5Return JSON, and take a parameter#
Real APIs return JSON objects, not bare strings, and they read input from the URL. Add one more method to prove
both — no new file, just extend PingController for now (we'll split things properly next chapter).
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@GetMapping("/greet")
public Map<String, String> greet(@RequestParam(defaultValue = "world") String name) {
return Map.of("message", "hello, " + name);
}@RequestParam String name— pulls?name=...from the URL into the argument.defaultValuemakes it optional.- Returning a
Map(or any object) → Spring auto-converts it to JSON via a library called Jackson, which the web starter already included. You return Java; the client receives JSON. You never call a "toJson" yourself.
Section 6Worked micro-exercise — done with you, end to end#
This is the format the practice chapters use, shown here fully solved so you know exactly what "end to end" means. Read it as a demonstration, then do it on your own machine.
Goal. A GET on /health returns JSON
{"status":"UP","service":"taskflow"}.
- Visiting
/healthin the browser shows the JSON above. - It's a GET; no parameters.
Step by step — exactly what I did:
- Decide which layer this is.
It only speaks HTTP and returns fixed data → it's a Controller concern. No service or DB needed. So I add a method to a controller.
- Write the method.
A
@GetMapping("/health")returning aMapso Spring makes it JSON. - Run and verify against the checklist.
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "UP", "service", "taskflow");
}Self-check: both boxes tick. Notice you reused the exact pattern from
/greet minus the parameter. That reuse — spotting "this is the same shape as one I've done" — is
the skill the whole course is training.
Type Exercise 2.1 into your own project (don't copy-paste — typing builds the muscle) and get the green
output. Then delete /greet and /health if you like; /ping stays. Next
chapter we split responsibilities into real layers and this file gets a proper job.
Notebook Summary#
The full loop on one page
Generate#
- start.spring.io → Maven · Java · current stable Boot · Jar · Java 17+
- Group
com.example, artifacttaskflow→ packagecom.example.taskflow - One dependency: Spring Web → MVC + embedded Tomcat
Run#
./mvnw spring-boot:runor the green ▶ onmain()- Health lines: Tomcat started on port 8080 + Started TaskflowApplication
- 404 on
/before you add a route = normal.
First endpoint#
@RestControlleron the class +@GetMapping("/ping")on the method- Return a String → body; return an object/Map → JSON (via Jackson, already included)
@RequestParam→ read?name=from the URL
Box these#
- ⭐Every class must live under
com.example.taskflowor component scan won't find it (404 with no error). - ⚠
@Controller≠@RestController. The plain one returns a view name; you want the Rest one (returns the body). - 💡Port 8080 in use → app won't start. Set
server.port=8081inapplication.propertiesor free the port.
Chapter 3 — Controller → Service → Repository, wired. We turn this one-file toy into the real four-layer shape from the map, storing tasks in memory, and you'll watch dependency injection connect the layers for you.
TaskFlow checkpoint#
Current state: a package plan. Outcome: a running /ping endpoint. Prerequisite: the layer map; it guides you but does not lock this chapter. Java bridge: main() remains the entry point. Estimate: 35 minutes.
Mission: prove TaskFlow can receive a request#
Generate the app, add PingController, and use curl to see pong. The artifact is a repeatable local run.
./mvnw spring-boot:runstarts without an error.curl localhost:8080/pingreturnspong.- The controller sits below
com.example.taskflow.
Likely files: TaskflowApplication.java, controller/PingController.java. Suggested commit: feat: add taskflow ping endpoint.
Hint 1
Hint 2
@RestController, not a view controller.Hint 3
Before opening the solution: start once, curl once, and check the package name.
Reference patch — first endpoint
@GetMapping("/ping") is enough. Keep it transport-only.Recall before continuing#
- What starts the embedded server?
- Why must the controller share the base package?
- How do you prove the endpoint works without a browser?
The next workshop replaces this isolated endpoint with the map’s collaborating layers.