Creating a Basic REST API Using Java Spring Boot

Spring Boot removes the boilerplate that once made Java REST services tedious. With a few annotations, you can expose endpoints in minutes.

This guide walks you through a minimal yet production-ready setup. You will write code, not configuration.

Project Bootstrap with Spring Initializr

Open start.spring.io in any browser. Select Maven, Java, and the latest stable Spring Boot release.

Add only two dependencies: Spring Web and Spring Data JPA. These give you REST support and automatic repository generation.

Click Generate, unzip the folder, and open it in your IDE. The project is already compilable.

Folder Layout at First Glance

Source code lives under src/main/java. Resources such as application.properties sit beside it.

Do not touch the default package. Create a new sub-package named com.example.demo to keep your classes tidy.

Domain Model Design

Pick a simple concept like Task or Book. A single entity keeps the first API focused.

Create a Java class annotated with @Entity. Add private Long id and a few String fields.

Mark the id with @Id and @GeneratedValue. You now have a table without writing SQL.

Choosing Field Names

Use lowercase camelCase for columns. This matches JSON conventions and avoids mapping headaches.

Skip nullable=false at the start. Constraints can wait until you write integration tests.

Repository Layer in One Line

Create an interface that extends JpaRepository. Spring implements CRUD methods automatically.

No @Repository annotation is needed on the interface. The parent marker already brings it into the component scan.

Declare custom queries only when the method name grows awkward. Keep the first version tiny.

Naming Query Methods

Adding List findByCompletedTrue() works without code. The parser translates the camelCase into SQL.

Avoid the temptation to write @Query annotations early. Readable method names double as documentation.

Service Layer Traps

Many tutorials insert a service class between controller and repository. For CRUD APIs this layer often sits empty.

Start without it. Add @Transactional on the controller method if you need atomic writes.

When business rules appear, extract a service. Delayed abstraction costs less than premature layers.

Testing Without Services

Controllers can be unit-tested with @WebMvcTest. Mock the repository directly and skip the service mock.

This keeps test fixtures small and proves that your resource layer behaves correctly.

Controller Mapping Patterns

Annotate the class with @RequestMapping(“/api/tasks”). Keep the root shallow but versioned.

Use @GetMapping, @PostMapping, and so on for each verb. The code reads like a headline.

Return domain objects directly; Spring MessageConverters handle JSON for you.

Status Code Defaults

@PostMapping methods send 201 when the body is not null. Add @ResponseStatus(HttpStatus.CREATED) if you want to be explicit.

Returning ResponseEntity gives full control. Swap bodies and codes in the same method.

DTO versus Entity Exposure

Exposing entities couples your database schema to the public JSON. A renamed column breaks clients.

Create a mirror class named TaskDto with only the fields you want to reveal. Map by hand or with ModelMapper.

Keep the mapper call inside the controller method. Central mappers hide details and invite lazy updates.

Update DTOs

For partial updates, craft TaskUpdateDto that omits the id. Clients cannot accidentally move a record.

Null fields in the DTO signal “skip this column”. Your mapper can ignore them before merge.

Validation Without Ceremony

Add spring-boot-starter-validation to your pom. Annotate DTO fields with @NotBlank, @Size, or @Email.

Mark the controller parameter with @Valid. A 400 response arrives automatically on rule violations.

Custom messages go in ValidationMessages.properties. Keep keys short: blank.task.title.

Global Exception Handler

Create @ControllerAdvice class. Catch MethodArgumentNotValidException and build a clean map of field errors.

Return it inside ResponseEntity.badRequest(). Clients receive consistent JSON instead of a default trace.

Pageable Get Endpoints

Inject Pageable pageable into the handler method. Spring parses ?page=0&size=20 for you.

Return Page from the repository call. The JSON already contains totalElements and totalPages.

Sort by multiple fields with &sort=dueDate,asc&sort=priority,desc. No code changes required.

Preventing Heavy Requests

Add spring.data.web.pageable.max-page-size=100 in application.properties. Clients asking for 1000 receive a 400.

This guards your database without writing a validator.

Error Responses That Make Sense

Create a tiny ErrorResponse record with timestamp, path, status, and message fields. Build it once, reuse everywhere.

Throw a custom RecordNotFoundException from the controller. Catch it in the advice class and return 404.

Never expose stack traces in production. Set server.error.include-stacktrace=never.

Logging Internally

Inside the advice, log the trace at debug level. You keep diagnostics while users see calm JSON.

SLF4J’s parameterized placeholders avoid string concatenation cost: log.debug(“Missing id {}”, id).

Security Quick Win

Add spring-boot-starter-security to get a default login form. This locks everything until you decide.

Create a SecurityFilterChain bean and use http.csrf().disable() for stateless APIs. Permit POST to /api/tasks only with ROLE_ADMIN.

Store a test user in memory with {noop} prefix for the password. You can swap to JDBC later.

JWT Outline

When mobile clients arrive, switch to tokens. Issue JWT on a /login endpoint after checking credentials.

Filters validate the Authorization header. Spring converts claims into authorities for method-level @PreAuthorize.

Database Evolution with Flyway

Drop spring.jpa.hibernate.ddl-auto=update. Add flyway-core instead.

Create db/migration/V1__init.sql and define columns explicitly. You gain reproducible rollouts across environments.

Write V2__add_priority.sql later. Migrations run automatically on startup in the declared order.

Rollback Strategy

Flyway does not roll back. Provide a compensating script like V3__remove_priority.sql instead.

Test migrations on a copy of production data before release.

Profiles for Multiple Stages

Create application-dev.properties with spring.datasource.url pointing to a local Docker container.

Use application-prod.properties for cloud JDBC strings and logging.level.root=warn.

Activate with spring.profiles.active=prod on the java command line. No code changes, no risk of pushing dev settings.

Secret Management

Store passwords in environment variables. Spring reads them with ${DB_PASSWORD} placeholders.

Never commit credentials to Git, even in sample files.

Integration Testing Essentials

Use @SpringBootTest to spin up the full container. Autowired TestRestTemplate calls your endpoints over HTTP.

Annotate the test with @DirtiesContext if it inserts data. This prevents side effects between tests.

Keep an application-test.properties on the classpath. It can point to an in-memory H2 database for speed.

MockMvc versus Real Port

MockMvc is fast but skips Tomcat. Use it for unit-style controller tests.

TestRestTemplate hits the actual port, validating JSON serialization and error handlers.

Packaging and Running Jars

Run mvn clean package. The resulting fat jar contains Tomcat, so java -jar target/demo.jar starts the server instantly.

No web.xml, no external container, no complexity.

Containerization Snapshot

Write a Dockerfile that copies the jar and runs it with openjdk:17-jre-slim. Expose port 8080.

Multi-stage builds can compile inside Docker, keeping your host free of Maven.

Observing Health with Actuator

Add spring-boot-starter-actuator. /actuator/health returns {“status”:”UP”} by default.

Expose all endpoints in dev via management.endpoints.web.exposure.include=*. Hide them in prod.

Custom health indicators implement HealthIndicator and can check database connectivity or external services.

Metrics Endpoint

/actuator/metrics/jvm.memory.used gives live numbers. Pair it with Prometheus for scraping.

No code required; just add micrometer-registry-prometheus to the classpath.

CORS for Browser Clients

Without CORS, a browser blocks calls from another origin. Add @CrossOrigin(origins=”http://localhost:3000″) on the controller.

For global control, declare a WebMvcConfigurer bean and override addCorsMappings. Allow headers and methods explicitly.

Remember that CORS is enforced by browsers, not servers. Tools like curl ignore it.

Credentials Allowance

If your frontend sends cookies, set allowedOrigins to concrete domains and allowCredentials(true). Wildcards break cookie flows.

Serialization Gotchas

Infinite loops happen when Task has a List and SubTask points back to Task. Add @JsonIgnore on one side.

Alternatively, use @JsonManagedReference and @JsonBackReference for cleaner intent.

Expose IDs only when they are useful. Internal sequences can stay hidden to reduce coupling.

Custom Naming

Rename fields with @JsonProperty(“task_name”). This keeps Java style while matching external specs.

API Versioning Strategies

Prefix paths like /api/v1/tasks. The version lives in the URL, not the header, keeping calls copy-paste friendly.

When breaking changes arrive, clone the controller into a v2 package. Keep both running side by side.

Deprecate gradually using the Sun http response header. Clients notice and migrate.

Date Format Stability

Always use ISO-8601 strings for dates. Avoid locale formats that shift with server settings.

Performance Quick Fixes

Turn on compression in application.properties: server.compression.enabled=true. JSON shrinks by two thirds.

Add spring.jpa.open-in-view=false to prevent lazy loads during view rendering. Fetch what you need inside transactions.

Cache static lookup data with @Cacheable(“priorities”). A ConcurrentHashMap cache manager works for single nodes.

Select Before Optimize

Profile first. Spring Boot ships with a ready setup; do not tune blindly.

Final Polish

Document endpoints with springdoc-openapi-starter-webmvc-ui. Browse swagger-ui.html and share the link.

Write concise descriptions on each mapping. Good docs reduce support noise.

Commit often, tag releases, and let your pipeline deploy the jar. Your basic REST API is now alive, maintainable, and ready for growth.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *