diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7a845f6c9bc9970e3e8bcc4d1e2d6e2929c712a
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerCreateDto.java
@@ -0,0 +1,20 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class AnswerCreateDto {
+
+    @NotBlank
+    private String text;
+
+    @NotNull
+    private boolean correct;
+
+    @NotBlank
+    private String questionId;
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..433244900c287f01549b3e43a4971f68a86a2cc7
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerDto.java
@@ -0,0 +1,38 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.fuseri.model.dto.common.DomainObjectDto;
+
+import java.util.Objects;
+
+@AllArgsConstructor
+@Getter
+public class AnswerDto extends DomainObjectDto {
+
+    @NotBlank
+    private String text;
+
+    @NotNull
+    private boolean correct;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        AnswerDto answerDto = (AnswerDto) o;
+
+        if (correct != answerDto.correct) return false;
+        return Objects.equals(text, answerDto.text);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = text != null ? text.hashCode() : 0;
+        result = 31 * result + (correct ? 1 : 0);
+        return result;
+    }
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..229eea30d0c9f12e27237841064dbd92489f7758
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswerInQuestionCreateDto.java
@@ -0,0 +1,17 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class AnswerInQuestionCreateDto {
+
+    @NotBlank
+    private String text;
+
+    @NotNull
+    private boolean correct;
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..04996492928042452c248158a9d4cab9659a772a
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/AnswersCreateDto.java
@@ -0,0 +1,19 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Getter
+public class AnswersCreateDto {
+
+    @NotBlank
+    private String questionId;
+
+    @Valid
+    private List<AnswerInQuestionCreateDto> answers;
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..332b343dce447081c05db0a98e2c57df8815602e
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseCreateDto.java
@@ -0,0 +1,25 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class ExerciseCreateDto {
+
+    @NotBlank
+    private String name;
+
+    @NotBlank
+    private String description;
+
+    @NotNull
+    @PositiveOrZero
+    private int difficulty;
+
+    @NotBlank
+    private String courseId;
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff7abd444839ba89d15b50e72f31a3973b62ceef
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/ExerciseDto.java
@@ -0,0 +1,50 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.Getter;
+import lombok.Setter;
+import org.fuseri.model.dto.common.DomainObjectDto;
+
+import java.util.Objects;
+
+@Getter
+@Setter
+public class ExerciseDto extends DomainObjectDto {
+
+    @NotBlank
+    private String name;
+
+    @NotBlank
+    private String description;
+
+    @NotNull
+    @PositiveOrZero
+    private int difficulty;
+
+    @NotBlank
+    private String courseId;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ExerciseDto that = (ExerciseDto) o;
+
+        if (difficulty != that.difficulty) return false;
+        if (!Objects.equals(name, that.name)) return false;
+        if (!Objects.equals(description, that.description)) return false;
+        return Objects.equals(courseId, that.courseId);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (description != null ? description.hashCode() : 0);
+        result = 31 * result + difficulty;
+        result = 31 * result + (courseId != null ? courseId.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..738a9031739586351ba52b993c7522e0dbcb226d
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionCreateDto.java
@@ -0,0 +1,22 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Getter
+public class QuestionCreateDto {
+
+    @NotBlank
+    private String text;
+
+    @NotBlank
+    private String exerciseId;
+
+    @Valid
+    private List<AnswerInQuestionCreateDto> answers;
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cca20b14b60c9361eaea0893ff874500b690c61
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionDto.java
@@ -0,0 +1,44 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+import lombok.Setter;
+import org.fuseri.model.dto.common.DomainObjectDto;
+
+import java.util.List;
+import java.util.Objects;
+
+@Getter
+@Setter
+public class QuestionDto extends DomainObjectDto {
+
+    @NotBlank
+    private String text;
+
+    @NotBlank
+    private String exerciseId;
+
+    @Valid
+    private List<AnswerDto> answers;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        QuestionDto that = (QuestionDto) o;
+
+        if (!Objects.equals(text, that.text)) return false;
+        if (!Objects.equals(exerciseId, that.exerciseId)) return false;
+        return Objects.equals(answers, that.answers);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = text != null ? text.hashCode() : 0;
+        result = 31 * result + (exerciseId != null ? exerciseId.hashCode() : 0);
+        result = 31 * result + (answers != null ? answers.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..2efa520493c718e108a180fabae12a4887913cab
--- /dev/null
+++ b/application/model/src/main/java/org/fuseri/model/dto/exercise/QuestionUpdateDto.java
@@ -0,0 +1,16 @@
+package org.fuseri.model.dto.exercise;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+public class QuestionUpdateDto {
+
+    @NotBlank
+    private String text;
+
+    @NotBlank
+    private String exerciseId;
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/Answer.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/Answer.java
new file mode 100644
index 0000000000000000000000000000000000000000..12fac75aedf87d7c1f29fe7333a24762d19a9ad3
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/Answer.java
@@ -0,0 +1,21 @@
+package org.fuseri.moduleexercise.answer;
+
+import lombok.*;
+import org.fuseri.moduleexercise.common.DomainObject;
+
+/**
+ * Represent Answer entity
+ */
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Answer extends DomainObject {
+    private String text;
+
+    private boolean correct;
+
+    private String questionId;
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f2f75a06870be2aa1bb7898a0caa471197f1681
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerController.java
@@ -0,0 +1,89 @@
+package org.fuseri.moduleexercise.answer;
+
+import jakarta.persistence.EntityNotFoundException;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import org.fuseri.model.dto.exercise.AnswerCreateDto;
+import org.fuseri.model.dto.exercise.AnswerDto;
+import org.fuseri.model.dto.exercise.AnswersCreateDto;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.List;
+
+/**
+ * Represent a REST API controller for answers
+ * Handle HTTP requests related to answers
+ */
+@RestController
+@RequestMapping("/answers")
+public class AnswerController {
+
+    private final AnswerFacade facade;
+
+    @Autowired
+    public AnswerController(AnswerFacade facade) {
+        this.facade = facade;
+    }
+
+    /**
+     * Retrieve a list of AnswerDto objects which belong to question with questionId
+     *
+     * @param questionId the ID of the question for which to retrieve answers
+     * @return a List of AnswerDto objects
+     */
+    @GetMapping("/{question-id}")
+    public List<AnswerDto> findAllByQuestionId(@NotBlank @PathVariable("question-id") String questionId) {
+        return facade.findAllByQuestionId(questionId);
+    }
+
+    /**
+     * Create a new answer for the given question ID
+     *
+     * @param dto the AnswerCreateDto object containing information about the answer to create
+     * @return an AnswerDto object representing the newly created answer
+     * @throws ResponseStatusException if the question ID specified in the dto does not exist
+     */
+    @PostMapping
+    public List<AnswerDto> createMultiple(@Valid @RequestBody AnswersCreateDto dto) {
+        try {
+            return facade.createMultiple(dto);
+        } catch (EntityNotFoundException e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
+        }
+    }
+
+    /**
+     * Update an answer
+     *
+     * @param id  of answer to update
+     * @param dto dto with updated answer information
+     * @throws ResponseStatusException if the question id specified in the AnswerCreateDto dto does not exist
+     */
+    @PutMapping("/{id}")
+    public AnswerDto update(@NotBlank @PathVariable String id, @Valid @RequestBody AnswerCreateDto dto) {
+        try {
+            return facade.update(id, dto);
+        } catch (EntityNotFoundException e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND,
+                    e.getMessage());
+        }
+    }
+
+    /**
+     * Delete answer with the given id
+     *
+     * @param id of answer to delete
+     * @throws ResponseStatusException if answer with specified id does not exist
+     */
+    @DeleteMapping("/{id}")
+    public void delete(@NotBlank @PathVariable String id) {
+        try {
+            facade.delete(id);
+        } catch (EntityNotFoundException e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
+        }
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerFacade.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ef573dca41c6d90e1396484a1c54e29b1730073
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerFacade.java
@@ -0,0 +1,112 @@
+package org.fuseri.moduleexercise.answer;
+
+import org.fuseri.model.dto.exercise.AnswerCreateDto;
+import org.fuseri.model.dto.exercise.AnswerDto;
+import org.fuseri.model.dto.exercise.AnswersCreateDto;
+import org.fuseri.moduleexercise.question.Question;
+import org.fuseri.moduleexercise.question.QuestionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represent facade for managing answers
+ * Provide simplified interface for manipulating with answers
+ */
+@Service
+public class AnswerFacade {
+    private final AnswerService answerService;
+    private final QuestionService questionService;
+    private final AnswerMapper mapper;
+
+
+    /**
+     * Constructor for AnswerFacade
+     *
+     * @param answerService   the service responsible for handling answer-related logic
+     * @param questionService the service responsible for handling question-related logic
+     * @param mapper          the mapper responsible for converting between DTOs and entities
+     */
+    @Autowired
+    public AnswerFacade(AnswerService answerService, QuestionService questionService, AnswerMapper mapper) {
+        this.answerService = answerService;
+        this.questionService = questionService;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Retrieve a list of AnswerDto objects which belong to question with questionId
+     *
+     * @param questionId the ID of the question for which to retrieve answers
+     * @return a List of AnswerDto objects
+     */
+    public List<AnswerDto> findAllByQuestionId(String questionId) {
+        return mapper.toDtoList(answerService.findAllByQuestionId(questionId));
+    }
+
+    /**
+     * Create a new answer for the given question ID
+     *
+     * @param dto the AnswerCreateDto object containing information about the answer to create
+     * @return an AnswerDto object representing the newly created answer
+     */
+    public List<AnswerDto> createMultiple(@RequestBody AnswersCreateDto dto) {
+        List<Answer> createdAnswers = new ArrayList<>();
+        for (var answerDto : dto.getAnswers()) {
+            Question question;
+            question = questionService.find(dto.getQuestionId());
+
+            Answer answer = mapper.fromCreateDto(answerDto);
+            answer.setQuestionId(question.getId());
+            var createdAnswer = answerService.create(answer);
+            question.getAnswers().add(answer);
+            createdAnswers.add(createdAnswer);
+        }
+
+        return mapper.toDtoList(createdAnswers);
+    }
+
+    /**
+     * Update an answer
+     *
+     * @param id  of answer to update
+     * @param dto dto with updated answer information
+     */
+    public AnswerDto update(String id, AnswerCreateDto dto) {
+        var updatedAnswer = mapper.fromCreateDto(dto);
+        updatedAnswer.setId(id);
+        answerService.update(updatedAnswer);
+
+        Question question;
+        question = questionService.find(dto.getQuestionId());
+
+        var questionAnswers = question.getAnswers();
+        questionAnswers.removeIf(a -> a.getId().equals(id));
+        questionAnswers.add(updatedAnswer);
+        question.setAnswers(questionAnswers);
+        questionService.update(question);
+
+        return mapper.toDto(updatedAnswer);
+    }
+
+    /**
+     * Delete answer with the given id
+     *
+     * @param id of answer to delete
+     */
+    public void delete(String id) {
+        var answer = answerService.find(id);
+
+        Question question;
+        question = questionService.find(answer.getQuestionId());
+
+        var questionAnswers = question.getAnswers();
+        questionAnswers.removeIf(a -> a.getId().equals(answer.getId()));
+        question.setAnswers(questionAnswers);
+
+        answerService.delete(id);
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerMapper.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..3dec5ca627e8808d54e7f27593c834a73a0f5fa5
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerMapper.java
@@ -0,0 +1,29 @@
+package org.fuseri.moduleexercise.answer;
+
+import org.fuseri.model.dto.exercise.AnswerCreateDto;
+import org.fuseri.model.dto.exercise.AnswerDto;
+import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto;
+import org.fuseri.moduleexercise.common.DomainMapper;
+import org.mapstruct.Mapper;
+
+/**
+ * Mapper between Answers and their corresponding DTOs
+ */
+@Mapper
+public interface AnswerMapper extends DomainMapper<Answer, AnswerDto> {
+
+    /**
+     * Convert DTO of type AnswerCreateDto to Answer
+     *
+     * @param dto DTO to be converted
+     * @return corresponding Answer entity created from DTO
+     */
+    Answer fromCreateDto(AnswerCreateDto dto);
+
+    /**
+     * Convert DTO of type AnswerInQuestionCreateDto to Answer
+     * @param dto DTO to be converted
+     * @return corresponding Answer entity created from DTO
+     */
+    Answer fromCreateDto(AnswerInQuestionCreateDto dto);
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerRepository.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f1918988b192f6c0f0f5fe54f92544d4d72dcbf
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerRepository.java
@@ -0,0 +1,20 @@
+package org.fuseri.moduleexercise.answer;
+
+import org.fuseri.moduleexercise.common.DomainRepository;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+/**
+ * A repository interface for managing Answer entities
+ */
+public interface AnswerRepository extends DomainRepository<Answer, String> {
+
+    /**
+     * Find all answers to a question with the specified ID
+     *
+     * @param questionId the ID of the question to find answers for
+     * @return a list of all answers to the specified question
+     */
+    List<Answer> findByQuestionId(@Param("questionId") String questionId);
+}
\ No newline at end of file
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerRepositoryImpl.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerRepositoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a75a834add8e358abb04aa628ad9885a80d5705
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerRepositoryImpl.java
@@ -0,0 +1,28 @@
+package org.fuseri.moduleexercise.answer;
+
+import org.fuseri.moduleexercise.common.DomainRepositoryImpl;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * An implementation of the AnswerRepository interface
+ * Provides access to Answer entities stored in a data source
+ */
+@Repository
+public class AnswerRepositoryImpl extends DomainRepositoryImpl<Answer> implements AnswerRepository {
+
+    /**
+     * Find all answers to a question with the specified ID
+     *
+     * @param questionId the ID of the question to find answers for
+     * @return a list of all answers to the specified question
+     */
+    @Override
+    public List<Answer> findByQuestionId(String questionId) {
+        return getItems()
+                .stream()
+                .filter(e -> e.getQuestionId().equals(questionId))
+                .toList();
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..593656acfea40265f835d91d6b5f2572a8bdea31
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/answer/AnswerService.java
@@ -0,0 +1,57 @@
+package org.fuseri.moduleexercise.answer;
+
+import jakarta.persistence.EntityNotFoundException;
+import lombok.Getter;
+import org.fuseri.moduleexercise.common.DomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * Represent a service for managing Answer entities
+ */
+@Service
+public class AnswerService extends DomainService<Answer> {
+
+    /**
+     * The repository instance used by this service
+     */
+    @Getter
+    private final AnswerRepository repository;
+
+    /**
+     * Construct a new instance of AnswerService with the specified repository
+     *
+     * @param repository the repository instance to be used by this service
+     */
+    @Autowired
+    public AnswerService(AnswerRepository repository) {
+        this.repository = repository;
+    }
+
+    /**
+     * Retrieve a list of Answer entities with the specified question ID
+     *
+     * @param questionId the ID of the question to retrieve answers for
+     * @return a list of Answer entities with the specified question ID
+     */
+    @Transactional(readOnly = true)
+    public List<Answer> findAllByQuestionId(String questionId) {
+        return repository.findByQuestionId(questionId);
+    }
+
+    /**
+     * Retrieve the Answer entity with the specified id
+     *
+     * @param id the id of the Answer entity to retrieve
+     * @return the Answer entity with the specified id
+     * @throws EntityNotFoundException if no Answer entity exists with the specified id
+     */
+    @Transactional(readOnly = true)
+    public Answer find(String id) {
+        return repository.findById(id)
+                .orElseThrow(() -> new EntityNotFoundException("Answer '" + id + "' not found."));
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainMapper.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..91151e34a7604b75d8ec6e7ef3a4767bbfff811a
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainMapper.java
@@ -0,0 +1,59 @@
+package org.fuseri.moduleexercise.common;
+
+import org.fuseri.model.dto.common.DomainObjectDto;
+import org.fuseri.model.dto.common.Result;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.springframework.data.domain.Page;
+
+import java.util.List;
+
+/**
+ * Base interface for MapStruct Mappers
+ * It defines methods to convert DTOs to entities and vice versa
+ *
+ * @param <T> the type of the entity
+ * @param <S> the type of the DTO
+ */
+public interface DomainMapper<T extends DomainObject, S extends DomainObjectDto> {
+
+    /**
+     * Convert DTO to its corresponding entity
+     *
+     * @param dto DTO to be converted
+     * @return corresponding entity created from DTO
+     */
+    T fromDto(S dto);
+
+    /**
+     * Convert entity to its corresponding DTO
+     *
+     * @param entity entity to be converted
+     * @return corresponding DTO created from entity
+     */
+    S toDto(T entity);
+
+    /**
+     * Convert List of entities to List of corresponding DTOs
+     *
+     * @param entities entities to be converted
+     * @return corresponding list of DTO objects
+     */
+    List<S> toDtoList(List<T> entities);
+
+    /**
+     * Convert a {@link org.springframework.data.domain.Page} containing entities to a
+     * {@link Result} object containing a list of their corresponding DTO objects, along with
+     * pagination information
+     *
+     * @param source the Page of entities to be converted
+     * @return a Result object containing a list of DTO objects and pagination information
+     */
+    @Mappings({
+            @Mapping(target = "total", expression = "java(source.getTotalElements())"),
+            @Mapping(target = "page", expression = "java(source.getNumber())"),
+            @Mapping(target = "pageSize", expression = "java(source.getSize())"),
+            @Mapping(target = "items", expression = "java(toDtoList(source.getContent()))")
+    })
+    Result<S> toResult(Page<T> source);
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainObject.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainObject.java
new file mode 100644
index 0000000000000000000000000000000000000000..077a8e5cdb521f4c233572accf0e6a8bb10ac4d3
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainObject.java
@@ -0,0 +1,21 @@
+package org.fuseri.moduleexercise.common;
+
+import jakarta.persistence.Id;
+import jakarta.persistence.MappedSuperclass;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.UUID;
+
+/**
+ * Represent the base class for entities in the module.
+ */
+@Getter
+@Setter
+@MappedSuperclass
+public abstract class DomainObject {
+
+    @Id
+    private String id = UUID.randomUUID().toString();
+}
+
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainRepository.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..fae42adfae3eabea93352b01a26b25d36418bee1
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainRepository.java
@@ -0,0 +1,57 @@
+package org.fuseri.moduleexercise.common;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+
+import java.util.Optional;
+
+/**
+ * Dummy interface of repository. Later will be replaced by JpaRepository.
+ *
+ * @param <T>  entity
+ * @param <ID> entity ID
+ */
+public interface DomainRepository<T, ID> {
+
+    /**
+     * Save the specified entity
+     *
+     * @param entity entity to be saved
+     * @return created entity
+     */
+    T save(T entity);
+
+    /**
+     * Find entity by ID
+     *
+     * @param id ID of entity to be found
+     * @return {@code Optional} containing the found entity,
+     * or an empty {@code Optional} if no such entity exists
+     */
+    Optional<T> findById(ID id);
+
+    /**
+     * Retrieve a page of entities according to the specified pagination information
+     *
+     * @param pageRequest the pagination information for the query
+     * @return a page of entities that satisfy the pagination criteria
+     */
+    Page<T> findAll(PageRequest pageRequest);
+
+    /**
+     * Update entity
+     *
+     * @param entity entity to update
+     * @return updated entity
+     */
+    T update(T entity);
+
+    /**
+     * Delete the entity with the specified id
+     * Note that this does not do cascade deleting.
+     * We will have cascade deleting with usage of JpaRepository
+     *
+     * @param id the id of the entity to be deleted
+     */
+    void deleteById(ID id);
+}
\ No newline at end of file
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainRepositoryImpl.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainRepositoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c6b588fa6f6396062c93acc959d2d823284c129
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainRepositoryImpl.java
@@ -0,0 +1,106 @@
+package org.fuseri.moduleexercise.common;
+
+import jakarta.persistence.EntityNotFoundException;
+import lombok.Getter;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Dummy implementation of repository. Later will be replaced by JpaRepository.
+ *
+ * @param <T>  entity
+ */
+public abstract class DomainRepositoryImpl<T extends DomainObject> implements DomainRepository<T, String> {
+
+    /**
+     * Dummy database
+     */
+    @Getter
+    private final Set<T> items = new HashSet<>();
+
+    /**
+     * Save the specified entity
+     *
+     * @param entity entity to be saved
+     * @return created entity
+     */
+    @Override
+    public T save(T entity) {
+        items.add(entity);
+        return entity;
+    }
+
+    /**
+     * Find entity by ID
+     *
+     * @param id ID of entity to be found
+     * @return {@code Optional} containing the found entity,
+     * or an empty {@code Optional} if no such entity exists
+     */
+    @Override
+    public Optional<T> findById(String id) {
+        return items.stream()
+                .filter(e -> e.getId().equals(id))
+                .findFirst();
+    }
+
+    /**
+     * Retrieve a page of entities according to the specified pagination information
+     *
+     * @param pageRequest the pagination information for the query
+     * @return a page of entities that satisfy the pagination criteria
+     */
+    @Override
+    public Page<T> findAll(PageRequest pageRequest) {
+
+        int startIndex = pageRequest.getPageNumber() * pageRequest.getPageSize();
+
+        List<T> pageEntities = items.stream()
+                .skip(startIndex)
+                .limit(pageRequest.getPageSize())
+                .toList();
+
+        return new PageImpl<>(pageEntities, pageRequest, pageEntities.size());
+    }
+
+    /**
+     * Update entity
+     *
+     * @param entity entity to update
+     * @return updated entity
+     */
+    @Override
+    public T update(T entity) {
+        if (entity == null || entity.getId() == null) {
+            throw new IllegalArgumentException("Entity and its ID can not be null.");
+        }
+
+        var optionalEntity = findById(entity.getId());
+        if (optionalEntity.isEmpty()) {
+            throw new EntityNotFoundException("Entity not found with ID: " + entity.getId());
+        }
+
+        T oldEntity = optionalEntity.get();
+        items.remove(oldEntity);
+        items.add(entity);
+        return entity;
+    }
+
+    /**
+     * Delete the entity with the specified id
+     * Note that this does not do cascade deleting.
+     * We will have cascade deleting with usage of JpaRepository
+     *
+     * @param id the id of the entity to be deleted
+     */
+    @Override
+    public void deleteById(String id) {
+        items.removeIf(e -> e.getId().equals(id));
+    }
+}
\ No newline at end of file
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainService.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainService.java
new file mode 100644
index 0000000000000000000000000000000000000000..badd21263def20ee0cf660c2be3f1f156a84aed4
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/common/DomainService.java
@@ -0,0 +1,49 @@
+package org.fuseri.moduleexercise.common;
+
+/**
+ * Represent common service for managing entities
+ *
+ * @param <T> the type of entity managed by this service
+ */
+public abstract class DomainService<T extends DomainObject> {
+
+    /**
+     * The default page size used for pagination
+     */
+    public static final int DEFAULT_PAGE_SIZE = 10;
+
+    /**
+     * Return the repository used by this service
+     *
+     * @return the repository used by this service
+     */
+    public abstract DomainRepository<T, String> getRepository();
+
+    /**
+     * Create an entity by saving it to the repository
+     *
+     * @param entity the entity to create
+     * @return the created entity
+     */
+    public T create(T entity) {
+        return getRepository().save(entity);
+    }
+
+    /**
+     * Update an entity
+     *
+     * @param entity the entity to update
+     * @return the updated entity
+     */
+    public T update(T entity) {
+        return getRepository().update(entity);
+    }
+
+    /**
+     * Delete an entity with specified id
+     * @param id id of the entity to delete
+     */
+    public void delete(String id) {
+        getRepository().deleteById(id);
+    }
+}
\ No newline at end of file
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/Exercise.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/Exercise.java
new file mode 100644
index 0000000000000000000000000000000000000000..65ff0e59f9a31773168eaf378000bac45f010fd0
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/Exercise.java
@@ -0,0 +1,49 @@
+package org.fuseri.moduleexercise.exercise;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.fuseri.moduleexercise.common.DomainObject;
+import org.fuseri.moduleexercise.question.Question;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represent exercise entity
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@Builder
+public class Exercise extends DomainObject {
+
+    private String name;
+
+    private String description;
+
+    private int difficulty;
+
+    private String courseId;
+
+    private Set<Question> questions = new HashSet<>();
+
+    /**
+     * Constructor of exercise
+     *
+     * @param name        exercise name
+     * @param description exercise description
+     * @param difficulty  exercise's difficulty
+     * @param courseId    id of lecture to which exercise belongs
+     * @param questions   question exercise contains
+     */
+    public Exercise(String name, String description, int difficulty, String courseId, Set<Question> questions) {
+        this.name = name;
+        this.description = description;
+        this.difficulty = difficulty;
+        this.courseId = courseId;
+        this.questions = Objects.requireNonNullElseGet(questions, HashSet::new);
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a53e6f9630c53ba29d16275f982202628181fbf
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseController.java
@@ -0,0 +1,124 @@
+package org.fuseri.moduleexercise.exercise;
+
+import jakarta.persistence.EntityNotFoundException;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.PositiveOrZero;
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.model.dto.exercise.ExerciseCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseDto;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ResponseStatusException;
+
+/**
+ * Represent a REST API controller for exercises
+ * Handle HTTP requests related to exercises
+ */
+@RestController
+@RequestMapping("/exercises")
+public class ExerciseController {
+
+    private final ExerciseService service;
+
+    private final ExerciseMapper mapper;
+
+    /**
+     * Constructor for AnswerController
+     *
+     * @param service the service responsible for handling exercise-related logic
+     * @param mapper  the mapper responsible for converting between DTOs and entities
+     */
+    @Autowired
+    public ExerciseController(ExerciseService service, ExerciseMapper mapper) {
+        this.service = service;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Create a new answer for the given question ID
+     *
+     * @param dto the ExerciseCreateDto object containing information about the exercise to create
+     * @return an ExerciseDto object representing the newly created exercise
+     */
+    @PostMapping
+    public ExerciseDto create(@Valid @RequestBody ExerciseCreateDto dto) {
+        Exercise exercise = mapper.fromCreateDto(dto);
+        return mapper.toDto(service.create(exercise));
+    }
+
+    /**
+     * Find an exercise by ID
+     *
+     * @param id the ID of the exercise to find
+     * @return an ExerciseDto object representing the found exercise
+     */
+    @GetMapping("/{id}")
+    public ExerciseDto find(@NotBlank @PathVariable String id) {
+        return mapper.toDto(service.find(id));
+    }
+
+    /**
+     * Find exercises and return them in a paginated format
+     *
+     * @param page the page number of the exercises to retrieve
+     * @return a Result object containing a list of ExerciseDto objects and pagination information
+     */
+    @GetMapping
+    public Result<ExerciseDto> findAll(@PositiveOrZero @RequestParam int page) {
+        Page<Exercise> exercise = service.findAll(page);
+        return mapper.toResult(exercise);
+    }
+
+    /**
+     * Find exercises that mach filters and return them in paginated format
+     *
+     * @param page       the page number of the exercises to retrieve
+     * @param courseId   the id of the course to filter by
+     * @param difficulty the difficulty level to filter by
+     * @return a Result object containing a list of filtered ExerciseDto objects woth pagination information
+     */
+    @GetMapping("filter")
+    public Result<ExerciseDto> findPerDifficultyPerCourse(
+            @PositiveOrZero @RequestParam int page, @NotBlank @RequestParam String courseId,
+            @PositiveOrZero @RequestParam int difficulty) {
+        Page<Exercise> exercise = service.findPerDifficultyPerCourse(page, courseId, difficulty);
+        return mapper.toResult(exercise);
+    }
+
+    /**
+     * Update an exercise with id
+     *
+     * @param id  the ID of the exercise to update
+     * @param dto the ExerciseCreateDto object containing information about the exercise to update
+     * @return an ExerciseDto object representing the updated exercise
+     * @throws ResponseStatusException invalid exercise id
+     */
+
+    @PutMapping("/{id}")
+    public ExerciseDto update(@NotBlank @PathVariable String id, @Valid @RequestBody ExerciseCreateDto dto) {
+        Exercise exercise = mapper.fromCreateDto(dto);
+        exercise.setId(id);
+
+        try {
+            return mapper.toDto(service.update(exercise));
+        } catch (IllegalArgumentException e) {
+            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
+        } catch (EntityNotFoundException e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
+        }
+    }
+
+    /**
+     * Delete an exercise with exerciseId
+     *
+     * @param id the ID of the exercise to delete
+     */
+    @DeleteMapping("/{id}")
+    public void delete(@NotBlank @PathVariable String id) {
+        service.delete(id);
+    }
+
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseMapper.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c211aaa5d034ecba3b54e738da0ebb4315b51bf
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseMapper.java
@@ -0,0 +1,21 @@
+package org.fuseri.moduleexercise.exercise;
+
+import org.fuseri.model.dto.exercise.ExerciseCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseDto;
+import org.fuseri.moduleexercise.common.DomainMapper;
+import org.mapstruct.Mapper;
+
+/**
+ * Mapper between Exercises and their corresponding DTOs
+ */
+@Mapper
+public interface ExerciseMapper extends DomainMapper<Exercise, ExerciseDto> {
+
+    /**
+     * Convert DTO of type ExerciseCreateDto to Exercise
+     *
+     * @param dto DTO to be converted
+     * @return corresponding Exercise entity created from DTO
+     */
+    Exercise fromCreateDto(ExerciseCreateDto dto);
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseRepository.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..76ec2a3a1eaf05eb979ccd4cdd8d5267397addb3
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseRepository.java
@@ -0,0 +1,24 @@
+package org.fuseri.moduleexercise.exercise;
+
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.moduleexercise.common.DomainRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+
+/**
+ * A repository interface for managing Exercise entities
+ */
+public interface ExerciseRepository extends DomainRepository<Exercise, String> {
+
+    /**
+     * Filters the exercises by the specified difficulty level and course id,
+     * and returns a {@link Result} object containing these filtered exercises
+     * along with pagination information
+     *
+     * @param pageRequest the pagination settings for the result
+     * @param courseId    the id of the course to filter by
+     * @param difficulty  the difficulty level to filter by
+     * @return a {@link Result} object containing a list of paginated exercises that match the filter criteria
+     */
+    Page<Exercise> filterPerDifficultyPerCourse(PageRequest pageRequest, String courseId, int difficulty);
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseRepositoryImpl.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseRepositoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..3036f93b02043658d38c332c45d2cbc154f308c6
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseRepositoryImpl.java
@@ -0,0 +1,42 @@
+package org.fuseri.moduleexercise.exercise;
+
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.moduleexercise.common.DomainRepositoryImpl;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * An implementation of the ExerciseRepository interface
+ * Provides access to Exercise entities stored in a data source
+ */
+@Repository
+public class ExerciseRepositoryImpl extends DomainRepositoryImpl<Exercise> implements ExerciseRepository {
+
+    /**
+     * Filters the exercises by the specified difficulty level and course id,
+     * and returns a {@link Result} object containing these filtered exercises
+     * along with pagination information
+     *
+     * @param pageRequest the pagination settings for the result
+     * @param courseId    the id of the course to filter by
+     * @param difficulty  the difficulty level to filter by
+     * @return a {@link Result} object containing a list of paginated exercises that match the filter criteria
+     */
+    @Override
+    public Page<Exercise> filterPerDifficultyPerCourse(PageRequest pageRequest, String courseId, int difficulty) {
+
+        int startIndex = pageRequest.getPageNumber() * pageRequest.getPageSize();
+
+        List<Exercise> pageEntities = getItems().stream()
+                .filter(e -> e.getCourseId().equals(courseId) && e.getDifficulty() == difficulty)
+                .skip(startIndex)
+                .limit(pageRequest.getPageSize())
+                .toList();
+
+        return new PageImpl<>(pageEntities, pageRequest, pageEntities.size());
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseService.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseService.java
new file mode 100644
index 0000000000000000000000000000000000000000..677c704a7892b13672c8534c0d731d3ba7a2abb6
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/exercise/ExerciseService.java
@@ -0,0 +1,70 @@
+package org.fuseri.moduleexercise.exercise;
+
+import jakarta.persistence.EntityNotFoundException;
+import lombok.Getter;
+import org.fuseri.moduleexercise.common.DomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Represent a service for managing Exercise entities
+ */
+@Service
+public class ExerciseService extends DomainService<Exercise> {
+
+    /**
+     * The repository instance used by this service
+     */
+    @Getter
+    private final ExerciseRepository repository;
+
+    /**
+     * Construct a new instance of ExerciseService with the specified repository
+     *
+     * @param repository the repository instance to be used by this service
+     */
+    @Autowired
+    public ExerciseService(ExerciseRepository repository) {
+        this.repository = repository;
+    }
+
+    /**
+     * Retrieve the Exercise entity with the specified ID
+     *
+     * @param id the ID of the Exercise entity to retrieve
+     * @return the Exercise entity with the specified ID
+     * @throws EntityNotFoundException if no Exercise entity exists with the specified ID
+     */
+    @Transactional(readOnly = true)
+    public Exercise find(String id) {
+        return repository.findById(id)
+                .orElseThrow(() -> new EntityNotFoundException("Exercise '" + id + "' not found."));
+    }
+
+    /**
+     * Retrieve a page of Exercise entities
+     *
+     * @param page the page number to retrieve (0-indexed)
+     * @return a page of Exercise entities
+     */
+    @Transactional(readOnly = true)
+    public Page<Exercise> findAll(int page) {
+        return repository.findAll(PageRequest.of(page, DEFAULT_PAGE_SIZE));
+    }
+
+    /**
+     * Retrieve a page of exercises filtered by the specified course id and difficulty level
+     *
+     * @param page       the page number to retrieve
+     * @param courseId   the id of the course to filter by
+     * @param difficulty the difficulty level to filter by
+     * @return a {@link Page} of {@link Exercise} objects filtered by the specified course id and difficulty level
+     */
+    public Page<Exercise> findPerDifficultyPerCourse(int page, String courseId, int difficulty) {
+        return repository.filterPerDifficultyPerCourse(
+                PageRequest.of(page, DEFAULT_PAGE_SIZE), courseId, difficulty);
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java
new file mode 100644
index 0000000000000000000000000000000000000000..042604af6ad1e339be34b38f5481806938dfbb38
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/Question.java
@@ -0,0 +1,41 @@
+package org.fuseri.moduleexercise.question;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.fuseri.moduleexercise.answer.Answer;
+import org.fuseri.moduleexercise.common.DomainObject;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represent Question entity
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@Builder
+public class Question extends DomainObject {
+
+    private String text;
+
+    private Set<Answer> answers = new HashSet<>();
+
+    private String exerciseId;
+
+    /**
+     * Constructor of question
+     *
+     * @param text       question text
+     * @param answers    question answers
+     * @param exerciseId id of exercise the question belongs to
+     */
+    public Question(String text, Set<Answer> answers, String exerciseId) {
+        this.text = text;
+        this.answers = Objects.requireNonNullElseGet(answers, HashSet::new);
+        this.exerciseId = exerciseId;
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd217cc1c00f36d02b1b074b6e9028c20e291220
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionController.java
@@ -0,0 +1,104 @@
+package org.fuseri.moduleexercise.question;
+
+import jakarta.persistence.EntityNotFoundException;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.PositiveOrZero;
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.model.dto.exercise.QuestionCreateDto;
+import org.fuseri.model.dto.exercise.QuestionDto;
+import org.fuseri.model.dto.exercise.QuestionUpdateDto;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ResponseStatusException;
+
+/**
+ * Represent a REST API controller for questions
+ * Handle HTTP requests related to questions
+ */
+@RestController
+@RequestMapping("/questions")
+public class QuestionController {
+
+    private final QuestionFacade questionFacade;
+
+    /**
+     * Constructor for QuestionController
+     *
+     * @param questionFacade the service responsible for handling question-related logic
+     */
+    @Autowired
+    public QuestionController(QuestionFacade questionFacade) {
+        this.questionFacade = questionFacade;
+    }
+
+    /**
+     * Find a question by ID.
+     *
+     * @param id the ID of the question to find
+     * @return a QuestionDto object representing the found question
+     */
+    @GetMapping("/{id}")
+    public QuestionDto find(@NotBlank @PathVariable String id) {
+        return questionFacade.find(id);
+    }
+
+    /**
+     * Find questions by exercise ID and return them in a paginated format
+     *
+     * @param exerciseId the ID of the exercise to find questions for
+     * @param page       the page number of the questions to retrieve
+     * @return a Result object containing a list of QuestionDto objects and pagination information
+     */
+    @GetMapping("/exercise/{exercise-id}")
+    public Result<QuestionDto> findByExerciseId(@NotBlank @PathVariable("exercise-id") String exerciseId,
+                                                @PositiveOrZero @RequestParam int page) {
+        return questionFacade.findByExerciseId(exerciseId, page);
+    }
+
+    /**
+     * Add a new question to an exercise
+     *
+     * @param dto a QuestionCreateDto object representing the new question to add
+     * @return a QuestionDto object representing the added question
+     * @throws ResponseStatusException if the exercise with exerciseId from QuestionCreateDto does not exist
+     */
+    @PostMapping
+    public QuestionDto addQuestion(@Valid @RequestBody QuestionCreateDto dto) {
+        try {
+            return questionFacade.create(dto);
+        } catch (EntityNotFoundException e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND,
+                    e.getMessage());
+        }
+    }
+
+    /**
+     * Update question
+     *
+     * @param dto a QuestionDto object representing the updated question with correct id
+     * @return a QuestionUpdateDto object representing the updated question
+     * @throws ResponseStatusException if the question with id doesn't exist or its exercise doesn't exist
+     */
+    @PutMapping("/{id}")
+    public QuestionDto updateQuestion(@NotBlank @PathVariable String id, @Valid @RequestBody QuestionUpdateDto dto) {
+        try {
+            return questionFacade.update(id, dto);
+        } catch (IllegalArgumentException e) {
+            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
+        } catch (EntityNotFoundException e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
+        }
+    }
+
+    /**
+     * Add a new question to an exercise
+     *
+     * @param id of question to delete
+     */
+    @DeleteMapping("/{id}")
+    public void deleteQuestion(@NotBlank @PathVariable String id) {
+        questionFacade.delete(id);
+    }
+}
\ No newline at end of file
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f2cfc9f4219b69c9c475ecc70c6c4de428233a5
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionFacade.java
@@ -0,0 +1,126 @@
+package org.fuseri.moduleexercise.question;
+
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.model.dto.exercise.QuestionCreateDto;
+import org.fuseri.model.dto.exercise.QuestionDto;
+import org.fuseri.model.dto.exercise.QuestionUpdateDto;
+import org.fuseri.moduleexercise.answer.Answer;
+import org.fuseri.moduleexercise.answer.AnswerMapper;
+import org.fuseri.moduleexercise.answer.AnswerService;
+import org.fuseri.moduleexercise.exercise.Exercise;
+import org.fuseri.moduleexercise.exercise.ExerciseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Represent facade for managing questions
+ * Provide simplified interface for manipulating with questions
+ */
+@Service
+public class QuestionFacade {
+    private final QuestionService questionService;
+    private final ExerciseService exerciseService;
+    private final AnswerService answerService;
+    private final QuestionMapper questionMapper;
+    private final AnswerMapper answerMapper;
+
+    @Autowired
+    public QuestionFacade(
+            QuestionService questionService, ExerciseService exerciseService,
+            AnswerService answerService, QuestionMapper questionMapper,
+            AnswerMapper answerMapper) {
+        this.questionService = questionService;
+        this.exerciseService = exerciseService;
+        this.answerService = answerService;
+        this.questionMapper = questionMapper;
+        this.answerMapper = answerMapper;
+    }
+
+    /**
+     * Find a question by ID.
+     *
+     * @param id the ID of the question to find
+     * @return a QuestionDto object representing the found question
+     */
+    public QuestionDto find(String id) {
+        var a = questionService.find(id);
+        return questionMapper.toDto(a);
+    }
+
+    /**
+     * Find questions by exercise ID and return them in a paginated format
+     *
+     * @param exerciseId the ID of the exercise to find questions for
+     * @param page       the page number of the questions to retrieve
+     * @return a Result object containing a list of QuestionDto objects and pagination information
+     */
+    public Result<QuestionDto> findByExerciseId(String exerciseId, int page) {
+        Page<Question> questions = questionService.findByExerciseId(exerciseId, page);
+        return questionMapper.toResult(questions);
+    }
+
+    /**
+     * Create a new question
+     *
+     * @param dto a QuestionCreateDto object representing the new question to add
+     * @return a QuestionDto object representing the added question
+     */
+    public QuestionDto create(QuestionCreateDto dto) {
+        Question question = questionMapper.fromCreateDto(dto);
+
+        Exercise exercise;
+        exercise = exerciseService.find(question.getExerciseId());
+
+        exercise.getQuestions().add(question);
+        question.setExerciseId(exercise.getId());
+
+        var answerDtos = dto.getAnswers();
+        var answers = new HashSet<Answer>();
+        for (var answerDto : answerDtos) {
+            Answer answer = answerMapper.fromCreateDto(answerDto);
+            answer = answerService.create(answer);
+            answers.add(answer);
+        }
+
+        question.setAnswers(answers);
+        var createdQuestion = questionService.create(question);
+
+        for (var answer : answers) {
+            answer.setQuestionId(createdQuestion.getId());
+        }
+
+        return questionMapper.toDto(createdQuestion);
+    }
+
+    /**
+     * Update question
+     *
+     * @param dto dto of updated question with correct id
+     * @return dto of updated question
+     */
+    public QuestionDto update(String id, QuestionUpdateDto dto) {
+        Question question = questionMapper.fromUpdateDto(dto);
+        question.setId(id);
+        List<Answer> questionAnswers = answerService.findAllByQuestionId(id);
+        question.setAnswers(new HashSet<>(questionAnswers));
+        Question updatedQuestion = questionService.update(question);
+        return questionMapper.toDto(updatedQuestion);
+    }
+
+    /**
+     * Delete question
+     *
+     * @param id of qustion to delete
+     */
+    public void delete(String id) {
+        var question = questionService.find(id);
+        for (var answer : question.getAnswers()) {
+            answerService.delete(answer.getId());
+        }
+        questionService.delete(id);
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionMapper.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c4e62a2a7845c32941b219a539030fcdbf98a16
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionMapper.java
@@ -0,0 +1,26 @@
+package org.fuseri.moduleexercise.question;
+
+import org.fuseri.model.dto.exercise.QuestionCreateDto;
+import org.fuseri.model.dto.exercise.QuestionDto;
+import org.fuseri.model.dto.exercise.QuestionUpdateDto;
+import org.fuseri.moduleexercise.common.DomainMapper;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+/**
+ * Mapper between Questions and their corresponding DTOs
+ */
+@Mapper
+public interface QuestionMapper extends DomainMapper<Question, QuestionDto> {
+
+    /**
+     * Convert DTO of type QuestionCreateDto to Question
+     *
+     * @param dto DTO to be converted
+     * @return corresponding Question entity created from DTO
+     */
+    @Mapping(target = "answers", ignore = true)
+    Question fromCreateDto(QuestionCreateDto dto);
+
+    Question fromUpdateDto(QuestionUpdateDto dto);
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionRepository.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f924cd5794fbfb300a826813de050a1dc7b4222
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionRepository.java
@@ -0,0 +1,20 @@
+package org.fuseri.moduleexercise.question;
+
+import org.fuseri.moduleexercise.common.DomainRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+
+/**
+ * A repository interface for managing Question entities
+ */
+public interface QuestionRepository extends DomainRepository<Question, String> {
+
+    /**
+     * Find a page of questions associated with the exercise with the specified ID
+     *
+     * @param exerciseId  the ID of the exercise to find questions for
+     * @param pageRequest the page request specifying the page number and page size
+     * @return a page of questions associated with the specified exercise
+     */
+    Page<Question> findByExerciseId(String exerciseId, PageRequest pageRequest);
+}
\ No newline at end of file
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionRepositoryImpl.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionRepositoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..703b89dcd133afb330a3468fb0e24cc60d0e41d4
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionRepositoryImpl.java
@@ -0,0 +1,36 @@
+package org.fuseri.moduleexercise.question;
+
+import org.fuseri.moduleexercise.common.DomainRepositoryImpl;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * An implementation of the QuestionRepository interface
+ * Provides access to Question entities stored in a data source
+ */
+@Repository
+public class QuestionRepositoryImpl extends DomainRepositoryImpl<Question> implements QuestionRepository {
+
+    /**
+     * Find a page of questions associated with the exercise with the specified ID
+     *
+     * @param exerciseId  the ID of the exercise to find questions for
+     * @param pageRequest the page request specifying the page number and page size
+     * @return a page of questions associated with the specified exercise
+     */
+    @Override
+    public Page<Question> findByExerciseId(String exerciseId, PageRequest pageRequest) {
+        List<Question> filteredQuestions = getItems()
+                .stream()
+                .filter(e -> e.getExerciseId().equals(exerciseId))
+                .skip(pageRequest.getOffset())
+                .limit(pageRequest.getPageSize())
+                .toList();
+
+        return new PageImpl<>(filteredQuestions, pageRequest, filteredQuestions.size());
+    }
+}
diff --git a/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java
new file mode 100644
index 0000000000000000000000000000000000000000..f30867aa5da680b2f61004a645c8fe1b21534161
--- /dev/null
+++ b/application/module-exercise/src/main/java/org/fuseri/moduleexercise/question/QuestionService.java
@@ -0,0 +1,61 @@
+package org.fuseri.moduleexercise.question;
+
+import jakarta.persistence.EntityNotFoundException;
+import lombok.Getter;
+import org.fuseri.moduleexercise.common.DomainService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Represent a service for managing Question entities
+ */
+@Service
+public class QuestionService extends DomainService<Question> {
+
+    /**
+     * The repository instance used by this service
+     */
+    @Getter
+    private final QuestionRepository repository;
+
+    /**
+     * Construct a new instance of QuestionService with the specified repository
+     *
+     * @param repository the repository instance to be used by this service
+     */
+    @Autowired
+    public QuestionService(QuestionRepository repository) {
+        this.repository = repository;
+    }
+
+    /**
+     * Retrieve the Question entity with the specified ID
+     *
+     * @param id the ID of the Question entity to retrieve
+     * @return the Question entity with the specified ID
+     * @throws EntityNotFoundException if no Question entity exists with the specified ID
+     */
+    @Transactional(readOnly = true)
+    public Question find(String id) {
+        return repository.findById(id)
+                .orElseThrow(() -> new EntityNotFoundException("Question '" + id + "' not found."));
+    }
+
+    /**
+     * Retrieve a page of Question entities associated with the specified exercise ID
+     *
+     * @param exerciseId the ID of the exercise to retrieve questions for
+     * @param page       the page number to retrieve (0-indexed)
+     * @return a page of Question entities associated with the specified exercise ID
+     */
+    @Transactional(readOnly = true)
+    public Page<Question> findByExerciseId(String exerciseId, int page) {
+        return repository.findByExerciseId(
+                exerciseId,
+                PageRequest.of(page, DomainService.DEFAULT_PAGE_SIZE));
+    }
+
+}
diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..da00608e87053cae0b248e864e0a5797d816f9bc
--- /dev/null
+++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/answer/AnswerTest.java
@@ -0,0 +1,173 @@
+package org.fuseri.moduleexercise.answer;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.fuseri.model.dto.exercise.AnswerDto;
+import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto;
+import org.fuseri.model.dto.exercise.AnswersCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseDto;
+import org.fuseri.model.dto.exercise.QuestionCreateDto;
+import org.fuseri.model.dto.exercise.QuestionDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import java.util.List;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class AnswerTest {
+
+    @Autowired
+    ObjectMapper objectMapper;
+    @Autowired
+    private MockMvc mockMvc;
+
+    public static String asJsonString(final Object obj) {
+        try {
+            return new ObjectMapper().writeValueAsString(obj);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private QuestionDto createQuestion(String id) throws Exception {
+        var question = new QuestionCreateDto("this statement is false", id,
+                List.of(new AnswerInQuestionCreateDto("dis a logical paradox", true)));
+
+
+        var posted = mockMvc.perform(post("/questions")
+                .content(asJsonString(question))
+                .contentType(MediaType.APPLICATION_JSON));
+
+        var cont = posted.andReturn().getResponse().getContentAsString();
+        var res = objectMapper.readValue(cont, QuestionDto.class);
+        return res;
+    }
+
+
+    private String createExercise() {
+        var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, "0");
+
+        String id = "";
+
+        try {
+            var dis = mockMvc.perform(post("/exercises")
+                    .content(asJsonString(postExercise))
+                    .contentType(MediaType.APPLICATION_JSON));
+            var ok = dis.andReturn().getResponse().getContentAsString();
+
+            var ll = objectMapper.readValue(ok, ExerciseDto.class);
+
+            id = ll.getId();
+        } catch (Exception e) {
+            assert (false);
+        }
+        return id;
+    }
+
+
+    @Test
+    void testCreateAnswer() throws Exception {
+
+        List<AnswerDto> res = createAnswer();
+
+        var expected1 = new AnswerDto("True", false);
+        var expected2 = new AnswerDto("False", false);
+
+        assert (res.get(0).equals(expected1));
+        assert (res.get(1).equals(expected2));
+
+    }
+
+    private List<AnswerDto> createAnswer() throws Exception {
+        var exerciseId = createExercise();
+        var question = createQuestion(exerciseId);
+
+        var incorrect1 = new AnswerInQuestionCreateDto("True", false);
+        var incorrect2 = new AnswerInQuestionCreateDto("False", false);
+
+        var createAnswer = new AnswersCreateDto(question.getId(), List.of(incorrect1, incorrect2));
+
+        var posted = mockMvc.perform(post("/answers")
+                .content(asJsonString(createAnswer))
+                .contentType(MediaType.APPLICATION_JSON));
+
+        var asStr = posted.andReturn().getResponse().getContentAsString();
+
+        var res = objectMapper.readValue(asStr, new TypeReference<List<AnswerDto>>() {
+        });
+        return res;
+    }
+
+
+    @Test
+    void getAnswer() throws Exception {
+        var exerciseId = createExercise();
+        var question = createQuestion(exerciseId);
+
+        var gets = mockMvc.perform(get(String.format("/answers/%s", question.getId())));
+
+        var content2 = gets.andReturn().getResponse().getContentAsString();
+
+        var res = objectMapper.readValue(content2, new TypeReference<List<AnswerDto>>() {
+        });
+
+        assert (res.equals(question.getAnswers()));
+
+    }
+
+    @Test
+    void testUpdate() throws Exception {
+
+        var exerciseId = createExercise();
+        var question = createQuestion(exerciseId);
+
+        var incorrect1 = new AnswerInQuestionCreateDto("True", false);
+        var incorrect2 = new AnswerInQuestionCreateDto("False", false);
+
+
+        var createAnswer = new AnswersCreateDto(question.getId(), List.of(incorrect1, incorrect2));
+
+
+        var posted = mockMvc.perform(post("/answers")
+                .content(asJsonString(createAnswer))
+                .contentType(MediaType.APPLICATION_JSON));
+
+        var asStr = posted.andReturn().getResponse().getContentAsString();
+
+        var res = objectMapper.readValue(asStr, new TypeReference<List<AnswerDto>>() {
+        });
+
+
+        var updated = """
+                {
+                  "text": "dis true",
+                  "correct": false,
+                  "questionId": "%s"
+                }
+                """;
+
+
+        updated = String.format(updated, question.getId());
+
+        var puts = mockMvc.perform(put(String.format("/answers/%s", res.get(0).getId()))
+                .content(updated).contentType(MediaType.APPLICATION_JSON));
+
+        var content = puts.andReturn().getResponse().getContentAsString();
+
+        var res2 = objectMapper.readValue(content, AnswerDto.class);
+
+        var expected = new AnswerDto("dis true", false);
+
+        assert res2.equals(expected);
+
+    }
+
+}
diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/exercise/ExerciseTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/exercise/ExerciseTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a634b41941ff09a1d6fa1a82125c2015c3206bf1
--- /dev/null
+++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/exercise/ExerciseTest.java
@@ -0,0 +1,161 @@
+package org.fuseri.moduleexercise.exercise;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.model.dto.exercise.ExerciseCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.Map;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class ExerciseTest {
+    @Autowired
+    ObjectMapper objectMapper;
+    @Autowired
+    private MockMvc mockMvc;
+
+    public static String asJsonString(final Object obj) {
+        try {
+            return new ObjectMapper().writeValueAsString(obj);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    @Test
+    void getExercise() {
+        var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, "0");
+
+        String id = "";
+
+        try {
+            var dis = mockMvc.perform(post("/exercises").content(asJsonString(postExercise)).contentType(MediaType.APPLICATION_JSON));
+            var ok = dis.andReturn().getResponse().getContentAsString();
+
+            var ll = objectMapper.readValue(ok, ExerciseDto.class);
+
+            id = ll.getId();
+        } catch (Exception e) {
+            assert (false);
+        }
+
+
+        try {
+            var theId = String.format("/exercises/%s", id);
+            var smth = mockMvc.perform(get(theId));
+
+        } catch (Exception e) {
+            //do absolutely nothing
+        }
+    }
+
+    @Test
+    void getFiltered() {
+
+
+        var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 0, "0");
+        var postExercise1 = new ExerciseCreateDto("idioms1", "exercise on basic idioms", 0, "0");
+        var postExercise2 = new ExerciseCreateDto("idioms2", "exercise on basic idioms", 1, "0");
+
+        try {
+            var exercise1 = mockMvc.perform(post("/exercises").content(asJsonString(postExercise)).contentType(MediaType.APPLICATION_JSON));
+
+            var exercise2 = mockMvc.perform(post("/exercises").content(asJsonString(postExercise1)).contentType(MediaType.APPLICATION_JSON));
+            var exercise3 = mockMvc.perform(post("/exercises").content(asJsonString(postExercise2)).contentType(MediaType.APPLICATION_JSON));
+        } catch (Exception e) {
+            //do absolutly nothing
+        }
+
+
+        Map<String, String> params;
+
+        try {
+            var filtered = mockMvc.perform(get("/exercises/filter").param("page", "0").param("courseId", "0").param("difficulty", "0"));
+
+            var content = filtered.andReturn().getResponse().getContentAsString();
+
+            var res = objectMapper.readValue(content, new TypeReference<Result<ExerciseDto>>() {
+            });
+
+            assert (res.getTotal() == 2);
+        } catch (Exception e) {
+            assert (false);
+        }
+    }
+
+    @Test
+    void testCreateExercise() throws Exception {
+        var expectedResponse = new ExerciseDto();
+        var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, "0");
+
+        mockMvc.perform(post("/exercises").content(asJsonString(postExercise)).contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("idioms")).andExpect(jsonPath("$.description").value("exercise on basic idioms")).andExpect(jsonPath("$.difficulty").value(2)).andExpect(jsonPath("$.courseId").value("0")).andReturn().getResponse().getContentAsString();
+    }
+
+    @Test
+    void testUpdate() {
+        var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, "0");
+
+        String id = "";
+
+        try {
+            var dis = mockMvc.perform(post("/exercises").content(asJsonString(postExercise)).contentType(MediaType.APPLICATION_JSON));
+            var ok = dis.andReturn().getResponse().getContentAsString();
+
+            var ll = objectMapper.readValue(ok, ExerciseDto.class);
+
+            id = ll.getId();
+        } catch (Exception e) {
+            assert (false);
+        }
+
+        var expectedExercise = new ExerciseDto();
+        expectedExercise.setId(id);
+        expectedExercise.setName("idioms");
+        expectedExercise.setDifficulty(2);
+        expectedExercise.setCourseId("idioms");
+        expectedExercise.setDescription("exercise on basic idioms");
+
+        var content = """
+                {
+                  "name": "idioms",
+                  "description": "exercise on basic idioms",
+                  "difficulty": 2,
+                  "courseId": "idioms"
+                }
+                """;
+
+
+        try {
+            var theId = String.format("/exercises/%s", id);
+            var dis = mockMvc.perform(put(theId).content(content).contentType(MediaType.APPLICATION_JSON));
+
+            var str = dis.andReturn().getResponse().getContentAsString();
+
+            var res = objectMapper.readValue(str, ExerciseDto.class);
+
+            assert res.equals(expectedExercise);
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+}
diff --git a/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionTest.java b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a099ff7c8cf058e033931222d5c76469ac40003e
--- /dev/null
+++ b/application/module-exercise/src/test/java/org/fuseri/moduleexercise/question/QuestionTest.java
@@ -0,0 +1,153 @@
+package org.fuseri.moduleexercise.question;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.fuseri.model.dto.common.Result;
+import org.fuseri.model.dto.exercise.AnswerDto;
+import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseCreateDto;
+import org.fuseri.model.dto.exercise.ExerciseDto;
+import org.fuseri.model.dto.exercise.QuestionCreateDto;
+import org.fuseri.model.dto.exercise.QuestionDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class QuestionTest {
+
+    @Autowired
+    ObjectMapper objectMapper;
+    @Autowired
+    private MockMvc mockMvc;
+
+    public static String asJsonString(final Object obj) {
+        try {
+            return new ObjectMapper().writeValueAsString(obj);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    void testCreateQuestion() throws Exception {
+        String id = createExercise();
+        var answr = new AnswerDto("dis a logical paradox", true);
+        QuestionDto res = createQuestion(id);
+        var expected = new QuestionDto();
+        expected.setAnswers(List.of(answr));
+        expected.setExerciseId(id);
+        expected.setId(res.getId());
+        expected.setText("this statement is false");
+
+        assert expected.equals(res);
+    }
+
+    private QuestionDto createQuestion(String id) throws Exception {
+        var question = new QuestionCreateDto("this statement is false", id, List.of(new AnswerInQuestionCreateDto("dis a logical paradox", true)));
+
+
+        var posted = mockMvc.perform(post("/questions").content(asJsonString(question)).contentType(MediaType.APPLICATION_JSON));
+
+
+        var cont = posted.andReturn().getResponse().getContentAsString();
+        var res = objectMapper.readValue(cont, QuestionDto.class);
+        return res;
+    }
+
+    private String createExercise() {
+        var postExercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, "0");
+
+        String id = "";
+
+        try {
+            var dis = mockMvc.perform(post("/exercises").content(asJsonString(postExercise)).contentType(MediaType.APPLICATION_JSON));
+            var ok = dis.andReturn().getResponse().getContentAsString();
+
+            var ll = objectMapper.readValue(ok, ExerciseDto.class);
+
+            id = ll.getId();
+        } catch (Exception e) {
+            assert (false);
+        }
+        return id;
+    }
+
+
+    @Test
+    void getQuestion() throws Exception {
+
+
+        String exerciseId = createExercise();
+        var question = createQuestion(exerciseId);
+
+        var theId = String.format("/questions/%s", question.getId());
+
+
+        var gets = mockMvc.perform(get(theId));
+
+        var content = gets.andReturn().getResponse().getContentAsString();
+        var res = objectMapper.readValue(content, QuestionDto.class);
+
+        assert res.equals(question);
+
+    }
+
+    @Test
+    void getByExercise() throws Exception {
+
+        var exerciseId = createExercise();
+        var question = createQuestion(exerciseId);
+
+        var theId = String.format("/questions/exercise/%s", exerciseId);
+
+        var smth = mockMvc.perform(get(theId).param("page", "0"));
+
+        var content = smth.andReturn().getResponse().getContentAsString();
+
+        var res = objectMapper.readValue(content, new TypeReference<Result<QuestionDto>>() {
+        });
+
+        Map<String, String> params;
+
+        assert (res.getItems().get(0).equals(question));
+    }
+
+    @Test
+    void TestUpdate() throws Exception {
+        String id = createExercise();
+        var question = createQuestion(id);
+
+        var updated = """
+                {
+                  "text": "wat a paradox?",
+                  "exerciseId": "%s"
+                }
+                """;
+
+        updated = String.format(updated, id);
+
+        var smth = mockMvc.perform(put(String.format("/questions/%s", question.getId())).content(updated).contentType(MediaType.APPLICATION_JSON));
+
+        var content = smth.andReturn().getResponse().getContentAsString();
+
+        var res = objectMapper.readValue(content, QuestionDto.class);
+
+        question.setText("wat a paradox?");
+
+        assert (question.equals(res));
+
+    }
+}
diff --git a/application/pom.xml b/application/pom.xml
index fbc2902e510c6776ba70a4695bbe1cf684610a9f..11c1c12c0a09887c996429d551d632c46f3260e1 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -72,6 +72,11 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springdoc</groupId>
             <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>