- Built a REST API in Java
- Practiced MVC-based project structure
- Implemented CRUD operations
- Worked with HTTP methods, paths, headers, and status codes
- Added a service layer for business logic
- Used Maven for dependency management
- Used Gson for JSON parsing
- Used Java Streams for filtering data
- Added unit tests with JUnit
- Integrated H2 database persistence
- Created a repository layer for database access
- Used prepared statements for safer SQL operations
- Added custom exception handling
- Used Mockito for testing with mocked dependencies
- Refactored the project using SOLID principles
- Discussed and applied design patterns
- Documented the development process and technical decisions
- Reviewed the base template and understood the application structure
- Identified the available API endpoints
- Prepared sample requests with cURL or Postman
- Explained the role of HTTP methods such as GET, POST, PUT, and DELETE
- Explained what HTTP headers are and where they are set in the application
- Understood the role of controller classes in handling HTTP requests
- Created a
modelspackage - Created the
Moviemodel class - Added private fields:
id,title,genre, andreleaseYear - Added a default constructor with automatic UUID generation
- Added a constructor with
title,genre, andreleaseYear - Added getters and setters
- Overrode the
toString()method - Created
generateDummyMovies()to generate sample movie data
- Created the
MovieControllerclass inside thecontrollerspackage - Implemented the
HttpHandlerinterface - Added endpoint handling logic inside the
handle()method - Returned
404 Path not foundfor unknown endpoints - Stored movie data in a movie list generated by
Movie.generateDummyMovies() - Implemented
GET /api/movies/getAll - Implemented
POST /api/movies/add - Implemented
DELETE /api/movies/delete - Implemented
PUT /api/movies/update - Returned correct success responses for CRUD operations
- Handled
400 Bad Requestfor invalid input - Handled
404 Not Foundfor missing movies - Handled
405 Method Not Allowedfor wrong HTTP methods
- Added
GET /api/movies/search - Created
parseQueryParams()insideApiUtils - Extracted query parameters from the URL
- Supported searching by
title,genre, andreleaseYear - Made search case-insensitive
- Supported partial string search
- Returned matching movies as JSON
- Created a
servicespackage - Created the
MovieServiceclass - Moved business logic from
MovieControllerintoMovieService - Injected the movie list through the
MovieServiceconstructor - Refactored filtering logic to use Java Streams and lambda expressions
- Updated
MovieControllerto callMovieServicemethods - Improved separation of concerns between controller and service layer
- Added Maven support
- Added the Gson dependency
- Replaced manual JSON parsing with Gson
- Used Gson to parse request bodies into
Movieobjects - Used Gson to generate JSON responses
- Added JUnit dependency
- Added Maven Surefire plugin
- Created unit tests for
MovieService - Used
@BeforeEachto prepare test data - Tested
addMovie - Tested
deleteMovie - Tested
updateMovie - Tested
searchMovies - Tested edge cases such as duplicate movies
- Tested partial search behavior
- Ran tests using
mvn test
- Added H2 database dependency
- Created
DatabaseUtil - Implemented
getConnection() - Added database schema initialization
- Created the
moviestable if it does not already exist - Used UUID as the primary key
- Stored movie data in the database instead of only in memory
- Created the
MovieRepositoryclass - Moved direct database access into the repository layer
- Implemented
add(Movie movie) - Implemented
findAll() - Implemented
delete(Movie movie) - Implemented
update(Movie movie) - Used SQL statements for CRUD operations
- Used
Connection.prepareStatement()for database queries - Improved persistence and database separation
- Refactored
MovieServiceto useMovieRepository - Injected
MovieRepositorythrough theMovieServiceconstructor - Updated CRUD methods to delegate database operations to the repository
- Verified database integration manually by adding, reading, updating, and deleting movies
- Created
MovieNotFoundException - Created
DatabaseException - Threw
MovieNotFoundExceptionwhen a movie could not be found - Threw
DatabaseExceptionfor SQL or database connection problems - Propagated exceptions from repository to service layer
- Propagated exceptions from service layer to controller layer
- Converted exceptions into meaningful HTTP responses
- Mapped
MovieNotFoundExceptionto404 Not Found - Mapped
DatabaseExceptionto500 Internal Server Error - Mapped malformed JSON errors to
400 Bad Request - Kept the application stable during runtime errors
- Added Mockito dependency
- Mocked
MovieRepositoryinMovieServiceTest - Updated service tests to work with the repository layer
- Tested successful service behavior with mocked repository data
- Tested
MovieNotFoundException - Tested
DatabaseException - Used
assertThrows()for exception tests - Verified that exceptions are correctly propagated
- Reviewed the application based on the SOLID principles
- Identified areas where the design could be improved
- Improved Single Responsibility Principle by keeping controller, service, repository, and model responsibilities separate
- Improved Open/Closed Principle by making parts of the application easier to extend without changing existing code
- Improved Liskov Substitution Principle by using abstractions where possible
- Improved Interface Segregation Principle by avoiding unnecessary dependencies between classes
- Improved Dependency Inversion Principle by injecting dependencies instead of creating them directly inside classes
- Explained why each refactoring improves maintainability
- Prepared explanations for the exercise demonstration
- Introduced or discussed a creational design pattern
- Used or discussed a factory-style approach for creating objects where useful
- Explained how the pattern helps object creation become cleaner and more flexible
- Introduced or discussed a structural design pattern
- Used or discussed repository-style separation as a structural improvement
- Explained how this improves the structure between service and database logic
- Introduced or discussed a behavioral design pattern
- Used or discussed a strategy-style approach for search or filtering logic
- Explained how this would make search behavior easier to extend
- Documented the SOLID improvements
- Documented the design patterns used or discussed
- Explained why the selected patterns are meaningful in this application
- Prepared to justify design decisions during the lecture demonstration
- Prepared to discuss possible alternatives and future improvements
# Movie REST API
A Java-based Movie REST API built step by step as part of the Programming 2 exercises.
The project started as a simple REST API for managing movies and gradually evolved into a more structured backend application with a controller layer, service layer, repository layer, H2 database persistence, exception handling, unit testing, mocking, and design pattern improvements.
## Project Overview
This application manages movies through a simple REST API. It supports creating, reading, updating, deleting, and searching movies.
The current architecture follows a layered structure:
```text
API Request
↓
Controller Layer
↓
Service Layer
↓
Repository Layer
↓
H2 DatabaseThe goal of the project was not only to make the API work, but also to improve the design step by step by applying clean architecture ideas, separation of concerns, exception propagation, testing strategies, SOLID principles, and design patterns.
- REST API for movie management
- MVC-inspired structure
- Controller layer for HTTP request handling
- Service layer for business logic
- Repository layer for database access
- H2 database integration
- Automatic database table initialization
- JSON parsing with Gson
- CRUD operations for movies
- Search endpoint with query parameters
- Prepared SQL statements
- Custom exception handling
- HTTP response mapping for errors
- Unit testing with JUnit
- Mocking with Mockito
- SOLID-based refactoring
- Design pattern improvements
- Java
- Maven
- H2 Database
- Gson
- JUnit
- Mockito
- Java HTTP Server
- IntelliJ IDEA
GET /api/movies/getAllReturns all movies stored in the database.
Example request:
curl -X GET http://localhost:8080/api/movies/getAllExample response:
[
{
"id": "200ce2c9-765d-4486-bdb1-bf68963d8142",
"title": "Inception",
"genre": "Sci-Fi",
"releaseYear": 2010
},
{
"id": "de122389-b7f5-4c61-8588-5b651fb31382",
"title": "The Silent Orbit",
"genre": "Sci-Fi",
"releaseYear": 2021
}
]POST /api/movies/addAdds a new movie to the database.
Example request:
curl -X POST http://localhost:8080/api/movies/add \
-H "Content-Type: application/json" \
-d '{"title":"Inception","genre":"Sci-Fi","releaseYear":2010}'Example response:
{
"message": "Movie added successfully"
}DELETE /api/movies/deleteDeletes a movie based on title, genre, and release year.
Example request:
curl -X DELETE http://localhost:8080/api/movies/delete \
-H "Content-Type: application/json" \
-d '{"title":"Inception","genre":"Sci-Fi","releaseYear":2010}'Example response:
{
"message": "Movie deleted successfully"
}If the movie does not exist:
{
"error": "Movie not found for deletion"
}PUT /api/movies/updateUpdates a movie using its ID.
Example request:
curl -X PUT http://localhost:8080/api/movies/update \
-H "Content-Type: application/json" \
-d '{
"id":"200ce2c9-765d-4486-bdb1-bf68963d8142",
"title":"Inception Updated",
"genre":"Sci-Fi",
"releaseYear":2010
}'Example response:
{
"message": "Movie updated successfully"
}GET /api/movies/searchSearches movies using query parameters.
Supported query parameters:
titlegenrereleaseYear
The search supports partial string matches and ignores upper/lower case.
Example requests:
curl "http://localhost:8080/api/movies/search?title=incep"curl "http://localhost:8080/api/movies/search?genre=Sci-Fi"curl "http://localhost:8080/api/movies/search?title=Inception&releaseYear=2010&genre=Sci-Fi"Example response:
[
{
"id": "200ce2c9-765d-4486-bdb1-bf68963d8142",
"title": "Inception",
"genre": "Sci-Fi",
"releaseYear": 2010
}
]The application uses an H2 database.
The database table is initialized automatically when the application starts.
CREATE TABLE IF NOT EXISTS movies (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL,
genre VARCHAR(100) NOT NULL,
release_year INT NOT NULL
);The repository layer uses prepared statements for all database operations.
Example:
String sql = "INSERT INTO movies (id, title, genre, release_year) VALUES (?, ?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, movie.getId());
statement.setString(2, movie.getTitle());
statement.setString(3, movie.getGenre());
statement.setInt(4, movie.getReleaseYear());Prepared statements are used because they:
- Separate SQL logic from user input
- Help prevent SQL injection
- Handle special characters correctly
- Make database code cleaner and safer
The application uses custom exceptions to keep error handling clear and meaningful.
Custom exceptions:
MovieNotFoundExceptionDatabaseException
The controller catches exceptions and converts them into proper HTTP responses.
| Exception | HTTP Response |
|---|---|
MovieNotFoundException |
404 Not Found |
DatabaseException |
500 Internal Server Error |
JsonSyntaxException |
400 Bad Request |
| Unknown Exception | 500 Internal Server Error |
Example error response:
{
"error": "Movie not found for deletion"
}The project includes unit tests for the service layer.
Testing tools:
- JUnit
- Mockito
After adding the repository layer, the service tests use a mocked MovieRepository instead of connecting to the real database.
This keeps the tests focused on service behavior and avoids depending on the database during unit tests.
Example:
when(movieRepository.delete(movieToDelete))
.thenThrow(new DatabaseException("Database connection error"));
assertThrows(DatabaseException.class, () -> {
movieService.deleteMovie(movieToDelete);
});The project was also analyzed and improved using the SOLID principles.
Each class has one main responsibility.
MovieControllerhandles HTTP requests and responses.MovieServicecontains business logic.MovieRepositoryhandles database operations.DatabaseUtilmanages database connection and initialization.- Custom exception classes represent specific error cases.
This makes the code easier to understand, test, and maintain.
The application can be extended without heavily modifying existing code.
For example, the service and repository structure makes it easier to add new operations later, such as filtering by director, rating, or runtime.
New behavior can be added in the service or repository without putting all logic inside the controller.
The application can be improved by depending on abstractions instead of concrete classes.
For example, a MovieRepositoryInterface could be introduced:
public interface MovieRepositoryInterface {
void add(Movie movie) throws DatabaseException;
List<Movie> findAll() throws DatabaseException;
boolean delete(Movie movie) throws DatabaseException, MovieNotFoundException;
boolean update(Movie movie) throws DatabaseException, MovieNotFoundException;
}Then MovieRepository can implement this interface.
This would make it possible to replace the database repository with another implementation, such as an in-memory repository for testing.
The repository and service interfaces should stay focused.
Instead of creating one large interface with many unrelated methods, the application should keep interfaces small and meaningful.
For this project, the movie-related methods belong together because they all work with the Movie model.
The service layer should not be tightly coupled to a concrete repository implementation.
The improved design is to inject the repository dependency into the service constructor.
Example:
public MovieService(MovieRepository movieRepository) {
this.movieRepository = movieRepository;
}This makes the service easier to test because a mock repository can be injected during unit tests.
The project can be explained using several design patterns.
A factory can be used to create service or repository objects in one central place.
For example, instead of creating dependencies directly in the controller, an application factory could create and wire them together.
Example idea:
public class AppFactory {
public static MovieService createMovieService() {
MovieRepository repository = new MovieRepository();
return new MovieService(repository);
}
}This helps centralize object creation and keeps the controller cleaner.
The repository pattern is used to separate database access from business logic.
MovieRepository hides the SQL details from the rest of the application.
The service does not need to know how SQL works. It only calls methods such as:
movieRepository.findAll();
movieRepository.add(movie);
movieRepository.delete(movie);
movieRepository.update(movie);This improves maintainability because database logic is isolated in one layer.
The search behavior can be improved with the Strategy Pattern.
For example, different search strategies could be created for:
- Search by title
- Search by genre
- Search by release year
- Combined search
Instead of putting all search conditions in one method, each strategy could handle one type of filtering.
Example idea:
public interface MovieSearchStrategy {
boolean matches(Movie movie, String value);
}This would make search logic easier to extend and test.
Clone the repository:
git clone https://github.com/YOUR_USERNAME/YOUR_REPOSITORY_NAME.git
cd YOUR_REPOSITORY_NAMEBuild the project:
mvn clean installRun the application from the Main class in IntelliJ.
The server runs on:
http://localhost:8080
Test the API:
curl -X GET http://localhost:8080/api/movies/getAllRun all tests with Maven:
mvn testOr run the test class directly in IntelliJ.
If you use H2 as a file-based database, avoid opening the database in IntelliJ's Database window while the Java server is running.
The database file can become locked.
Recommended workflow:
Testing API:
1. Disconnect IntelliJ Database
2. Run Java server
3. Use browser, Postman, or curl
Inspecting database:
1. Stop Java server
2. Open IntelliJ Database window
3. Refresh the H2 database
4. Check the MOVIES table
Example structure:
src
└── main
└── java
└── at.ac.fhcampuswien
├── Main.java
├── ApiUtils.java
├── controllers
│ └── MovieController.java
├── database
│ └── DatabaseUtil.java
├── exceptions
│ ├── DatabaseException.java
│ └── MovieNotFoundException.java
├── models
│ └── Movie.java
├── repositories
│ └── MovieRepository.java
└── services
└── MovieService.java
Through this project, I practiced:
- Building REST endpoints in Java
- Structuring a backend application
- Working with controller, service, and repository layers
- Using Gson for JSON parsing
- Connecting Java to an H2 database
- Writing SQL queries with prepared statements
- Handling checked exceptions
- Mapping application errors to HTTP responses
- Writing unit tests with JUnit
- Mocking dependencies with Mockito
- Applying SOLID principles
- Thinking about design patterns and software maintainability
Ghazal Arbabzadeh