Creating Your First Endpoint
Finally, let’s get our hands dirty. We’ll create a simple endpoint that returns a user: when we visit http://localhost:8080/users, we should get something like this:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz"
}
]
Step 0: Initialize Git
git init
git add .
git commit -m "Initial commit"
Step 1: Add a Formatter (Spotless)
-
Add the plugin in the plugins section (usually at the start of
build.gradle
):build.gradleid 'com.diffplug.spotless' version '6.25.0'
-
Add Spotless configuration (bottom of
build.gradle
). The following is my personal preference:build.gradlespotless {
java {
target 'src/*/java/**/*.java'
googleJavaFormat()
removeUnusedImports()
cleanthat()
formatAnnotations()
}
groovyGradle {
target '*.gradle'
greclipse()
}
}For further info about configurations, check Spotless GitHub repository.
-
Auto-format on every build, by adding a new task:
build.gradletasks.named("build") {
dependsOn 'spotlessApply'
dependsOn 'spotlessGroovyGradleApply'
} -
Now that everything is set up, run the Build Task.
-
Save the changes made so far:
git add .
git commit -m "spotless"
Step 2: Create the User Model
A model is a blueprint for your data — it defines the structure of the information your application handles.

Other folders are omitted for simplicity.
Create User.java
.
package dev.pollito.users_manager.domain.model;
import static lombok.AccessLevel.PRIVATE;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Builder
@Data
@FieldDefaults(level = PRIVATE)
public class User {
Long id;
String name;
String username;
String email;
}
We’ll use Lombok to avoid boilerplate code. Lombok automatically generates repetitive Java code at compile time.
- If your IDE doesn’t have the Lombok plugin installed, you’ll see compilation errors. Check Optimizing IntelliJ IDEA with Plugins (for Java) to find how to add the Lombok plugin.
Step 3: Create the Primary Port and its Implementation
UserSevice
is the Primary Port, defining user operations.UserSeviceImpl
is the implementation, containing domain logic.

Other folders are omitted for simplicity.
Create UserService.java
.
package dev.pollito.users_manager.domain.port.in;
import dev.pollito.users_manager.domain.model.User;
import java.util.List;
public interface UserService {
List<User> findAll();
}
Create UserServiceImpl.java
.
package dev.pollito.users_manager.domain.service;
import dev.pollito.users_manager.domain.model.User;
import dev.pollito.users_manager.domain.port.in.UserService;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private static final User USER_1 =
User.builder()
.id(1L)
.name("Leanne Graham")
.username("Bret")
.email("Sincere@april.biz")
.build();
@Override
public List<User> findAll() {
return List.of(USER_1);
}
}
- At the moment we are going to return a hardcoded user.
@Service
tells Spring here is an implementation ofUserService
.@Override
indicates that the methodpublic List<User> findAll()
fulfills the interface’s contract.
Step 4: Create the Primary Adapter
- The controller acts as a primary adapter, converting HTTP requests to calls on the domain service.
- The domain model is not exposed directly to the clients; instead, DTOs are used.

Other folders are omitted for simplicity.
Create UserResponseDTO.java
. It contains the data to be returned as a response.
package dev.pollito.users_manager.adapter.in.rest.dto;
import static lombok.AccessLevel.PRIVATE;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@FieldDefaults(level = PRIVATE)
public class UserResponseDTO {
Long id;
String name;
String username;
String email;
}
Create UserMapper.java
. It converts between domain models and DTOs.
package dev.pollito.users_manager.adapter.in.rest.dto;
import static java.util.Objects.isNull;
import dev.pollito.users_manager.domain.model.User;
import org.springframework.stereotype.Component;
@Component
public class UserMapper {
public UserResponseDTO map(User user) {
if (isNull(user)) {
return null;
}
return UserResponseDTO.builder()
.id(user.getId())
.name(user.getName())
.username(user.getUsername())
.email(user.getEmail())
.build();
}
}
Create UserController.java
. It acts as a primary adapter that converts HTTP requests to service calls.
package dev.pollito.users_manager.adapter.in.rest;
import dev.pollito.users_manager.adapter.in.rest.dto.UserResponseDTO;
import dev.pollito.users_manager.adapter.in.rest.mapper.UserMapper;
import dev.pollito.users_manager.domain.port.in.UserService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
@GetMapping
public ResponseEntity<List<UserResponseDTO>> findAll() {
return ResponseEntity.ok(userService.findAll().stream().map(userMapper::map).toList());
}
}
- Controller only depends on the service interface (port), not its implementation.
- Mapping logic is extracted to a separate mapper class.
- Domain objects are not leaked to the API clients.
Run the Application
Right-click the main class → Run. Then go to http://localhost:8080/users.
- Client invokes controller: An external client makes a GET request to the
/users
endpoint, which is handled by theUserController
. - Controller invokes Service: The
UserController
calls thefindAll()
method on the injectedUserService
instance (which is implemented byUserServiceImpl
). - Service returns to controller: The
UserServiceImpl
retrieves or generates the list ofUser
domain objects and returns it to theUserController
. - Note over controller: The
UserController
then processes the received list ofUser
objects. - Response sent to client: After mapping, the
UserController
wraps the list in aResponseEntity.ok()
and returns the final HTTP response (with a 200 OK status) to the client.

Congratulations! Your Spring Boot app is up, running, and exposing an endpoint. Commit the progress so far.
git add .
git commit -m "/users returns hardcoded user"