Tech Stack Configuration Skill
This skill provides comprehensive configuration context for the hello-spring-boot project. All new features should follow this established tech stack and architectural patterns.
Technology Stack
Core Framework:
- •Spring Boot: 3.4.4 (current version)
- •Java: 17 (LTS, required)
- •Gradle: 8.13
- •Build Tool: Gradle with Spring Boot plugin
Database Layer:
- •Database: PostgreSQL 15 (Docker container on port 5432)
- •Database Name: hello-spring-db
- •Username: postgres
- •Password: datpt
- •Connection Pool: HikariCP (max 10 connections)
- •ORM: Spring Data JPA with Hibernate
- •Migration Tool: Flyway for database version control
- •JDBC Driver: PostgreSQL 42.x
Testing:
- •Framework: JUnit 5
- •Mocking: Mockito
- •Spring Test: @SpringBootTest, @WebMvcTest, @DataJpaTest
- •Integration Testing: MockMvc for controller tests
Build & Utilities:
- •Lombok: For boilerplate reduction (@Data, @RequiredArgsConstructor, @Slf4j, etc.)
- •Config Format: application.properties (not YAML)
Containerization:
- •Docker & Docker Compose: For PostgreSQL + pgAdmin setup
- •pgAdmin 4: Port 5050 for database administration (email: admin@admin.com, password: admin)
Package Structure Pattern
Base Package: com.example.hellospringboot
Organization Style: Layer-based structure (NOT feature-based)
src/main/java/com/example/hellospringboot/
├── controller/ # REST API controllers
├── service/ # Business logic layer
├── repository/ # Spring Data JPA repositories
├── entity/ # JPA entities (@Entity)
├── dto/ # Data Transfer Objects (request/response)
├── exception/ # Custom exception classes
└── HelloSpringBootApplication.java # Main application entry point
src/main/resources/
├── application.properties # Configuration
└── db/migration/ # Flyway migration scripts
└── V{n}__{description}.sql
Important: The project uses a flat, layer-based package structure (NOT feature-based like com.example.app.student.controller).
Naming Conventions
Entity Naming
- •Entities: Singular noun (e.g.,
Student.java,Program.java) - •Table names: Lowercase, singular (e.g.,
@Table(name="student"),@Table(name="program")) - •Field names: camelCase in Java (e.g.,
firstName,lastName) - •Column names: Lowercase in database, mapped via
@Column(name="first_name")
Service Layer
- •Interface:
[Entity]Service.java(e.g.,StudentService.java) - •Implementation:
[Entity]ServiceImpl.java(e.g.,StudentServiceImpl.java) - •Annotation:
@Service - •Lombok:
@Slf4jand@RequiredArgsConstructorfor constructor injection
Repository Layer
- •Class name:
[Entity]Repository.java(e.g.,StudentRepository.java) - •Extension:
extends JpaRepository<[Entity], ID_Type> - •Annotation:
@Repository(optional, Spring auto-detects interfaces extending JpaRepository)
Controller Layer
- •Class name:
[Entity]Controller.java(e.g.,StudentController.java) - •Annotation:
@RestController - •Base path annotation:
@RequestMapping("/api/[plural-entity]")(e.g.,/api/students) - •Lombok:
@RequiredArgsConstructorfor constructor injection
Exception Classes
- •Pattern:
[Entity]NotFoundException.java,Invalid[Entity]Exception.java - •Example:
StudentNotFoundException.java - •Parent class: Extend
RuntimeException - •Global Handler: Use
@ControllerAdvicewith@ExceptionHandler
DTOs
- •Response:
[Entity]NameResponse.javaor[Entity]Response.java(e.g.,StudentNameResponse.java) - •Request:
[Entity]Request.java(e.g.,CreateStudentRequest.java) - •Error:
ErrorResponse.java(global error response DTO)
Code Patterns & Annotations
Service Layer Pattern
package com.example.hellospringboot.service;
import com.example.hellospringboot.dto.StudentResponse;
import com.example.hellospringboot.entity.Student;
import com.example.hellospringboot.exception.StudentNotFoundException;
import com.example.hellospringboot.repository.StudentRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {
private final StudentRepository studentRepository;
@Override
@Transactional(readOnly = true)
public StudentResponse getStudentById(Integer id) {
log.info("Getting student with ID: {}", id);
Student student = studentRepository.findById(id)
.orElseThrow(() -> new StudentNotFoundException("Student with ID " + id + " not found"));
log.info("Student retrieved successfully: ID={}", id);
return StudentResponse.from(student);
}
@Override
@Transactional
public StudentResponse createStudent(CreateStudentRequest request) {
log.info("Creating new student: {}", request.getName());
Student student = new Student();
student.setName(request.getName());
Student saved = studentRepository.save(student);
log.info("Student created successfully: ID={}", saved.getId());
return StudentResponse.from(saved);
}
}
Key patterns to follow:
- •Constructor injection with
@RequiredArgsConstructor(Lombok) - •
@Slf4jfor logging (Lombok annotation) - •Service interface with implementation class
- •
@Transactional(readOnly=true)for read operations - •
@Transactional(without params) for write operations - •Log important events (requests, successes, failures)
Controller Layer Pattern
package com.example.hellospringboot.controller;
import com.example.hellospringboot.dto.StudentResponse;
import com.example.hellospringboot.service.StudentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/students")
@RequiredArgsConstructor
@Slf4j
public class StudentController {
private final StudentService studentService;
@GetMapping("/{id}")
public ResponseEntity<StudentResponse> getStudent(@PathVariable Integer id) {
log.info("Received request to get student: ID={}", id);
StudentResponse response = studentService.getStudentById(id);
return ResponseEntity.ok(response);
}
@PostMapping
public ResponseEntity<StudentResponse> createStudent(@Valid @RequestBody CreateStudentRequest request) {
log.info("Received request to create student");
StudentResponse response = studentService.createStudent(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStudent(@PathVariable Integer id) {
log.info("Received request to delete student: ID={}", id);
studentService.deleteStudent(id);
return ResponseEntity.noContent().build();
}
}
Key patterns:
- •
@RestControllerannotation - •Constructor injection for services (
@RequiredArgsConstructor) - •
@RequestMappingfor base path - •
@GetMapping,@PostMapping,@PutMapping,@DeleteMappingfor endpoints - •
@PathVariablefor URL parameters - •
@RequestBodywith@Validfor request validation - •
ResponseEntityfor HTTP responses with proper status codes - •Logging at controller level
Repository Pattern
package com.example.hellospringboot.repository;
import com.example.hellospringboot.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.List;
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
// Spring Data JPA auto-implements findById(Integer id)
// Custom query methods following Spring Data naming conventions
Optional<Student> findByName(String name);
List<Student> findByNameContaining(String keyword);
boolean existsByName(String name);
}
Key patterns:
- •Extend
JpaRepository<Entity, ID_Type> - •Method naming follows Spring Data conventions
- •Return
Optional<T>for single results that might not exist - •Return
List<T>for multiple results - •Use
booleanfor existence checks (existsBy...)
Entity Pattern
package com.example.hellospringboot.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "name", length = 255, nullable = false)
private String name;
}
Key patterns:
- •
@Entityand@Tableannotations - •
@Datafrom Lombok for getters/setters/toString/equals/hashCode - •
@NoArgsConstructorand@AllArgsConstructorfrom Lombok - •
@Idfor primary key - •
@Columnannotations with constraints (length, nullable, unique) - •Use
@GeneratedValue(strategy = GenerationType.IDENTITY)for auto-increment IDs
Exception Handling Pattern
Custom Exception:
package com.example.hellospringboot.exception;
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(String message) {
super(message);
}
}
Global Exception Handler:
package com.example.hellospringboot.exception;
import com.example.hellospringboot.dto.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(StudentNotFoundException.class)
public ResponseEntity<ErrorResponse> handleStudentNotFound(StudentNotFoundException ex) {
log.warn("Student not found: {}", ex.getMessage());
ErrorResponse error = ErrorResponse.of(
"STUDENT_NOT_FOUND",
ex.getMessage()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDatabaseError(DataAccessException ex) {
log.error("Database error: {}", ex.getMessage(), ex);
ErrorResponse error = ErrorResponse.of(
"DATABASE_ERROR",
"Unable to process request due to database error"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericError(Exception ex) {
log.error("Unexpected error: {}", ex.getMessage(), ex);
ErrorResponse error = ErrorResponse.of(
"INTERNAL_ERROR",
"An unexpected error occurred"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
DTO Pattern
package com.example.hellospringboot.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentResponse {
private String id;
private String name;
public static StudentResponse from(Integer id, String name) {
return new StudentResponse(String.valueOf(id), name);
}
}
Validation Annotations
Used on DTO request classes:
import jakarta.validation.constraints.*; @NotNull(message = "Name is required") @NotBlank(message = "Name cannot be blank") @Size(min = 1, max = 255, message = "Name must be between 1 and 255 characters") @Pattern(regexp = "^[a-zA-Z0-9 ]+$", message = "Name can only contain letters, numbers, and spaces") @Email(message = "Email must be valid")
Database Migration with Flyway
Migration File Location: src/main/resources/db/migration/
Naming Convention: V{version}__{description}.sql
Examples:
- •
V1__Create_student_table.sql - •
V2__Add_program_table.sql - •
V3__Add_foreign_keys.sql
Migration Example:
-- V1__Create_student_table.sql
CREATE TABLE student (
id INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
Configuration in application.properties:
spring.flyway.enabled=true spring.flyway.baseline-on-migrate=true spring.flyway.locations=classpath:db/migration spring.jpa.hibernate.ddl-auto=validate
Important Rules:
- •Never modify existing migration files after they've been applied
- •Always create new migrations for schema changes
- •Hibernate uses
validatemode - it validates but doesn't auto-create/alter tables - •Use Flyway for all DDL changes (CREATE, ALTER, DROP)
HTTP Status Codes
Use these standard HTTP status codes in controllers:
| Code | Meaning | When to Use |
|---|---|---|
| 200 OK | Success | Successful GET, PUT (update) |
| 201 Created | Resource created | Successful POST (new resource) |
| 204 No Content | Success, no body | Successful DELETE |
| 400 Bad Request | Validation error | Invalid input, failed validation |
| 404 Not Found | Resource not found | Entity doesn't exist |
| 409 Conflict | Conflict | Duplicate entries, constraint violations |
| 500 Internal Server Error | Server error | Unexpected errors, database failures |
Example:
return ResponseEntity.ok(response); // 200 return ResponseEntity.status(HttpStatus.CREATED).body(response); // 201 return ResponseEntity.noContent().build(); // 204 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); // 404
Development Workflow
Build Commands
./gradlew build # Build the project ./gradlew bootRun # Run the application ./gradlew clean # Clean build artifacts ./gradlew build -x test # Build without tests
Testing
./gradlew test # Run all tests ./gradlew test --tests HelloSpringBootApplicationTests # Single test ./gradlew test --tests "*ControllerTests" # Pattern match
Database (Docker)
cd docker docker-compose up -d # Start PostgreSQL + pgAdmin docker-compose down # Stop services docker-compose logs -f # View logs docker-compose restart # Restart services
Access Information:
- •PostgreSQL:
localhost:5432(user: postgres, password: datpt, database: hello-spring-db) - •pgAdmin:
http://localhost:5050(email: admin@admin.com, password: admin)
Flyway Commands
./gradlew flywayInfo # Check migration status ./gradlew flywayValidate # Validate migrations ./gradlew flywayRepair # Repair migration history (use with caution) ./gradlew flywayClean # Clean database (WARNING: deletes all data)
Important Notes
Database Credentials
Development Only - Hardcoded in application.properties:
- •Host: localhost:5432
- •User: postgres
- •Password: datpt
- •Database: hello-spring-db
⚠️ Production: Use environment variables or secure credential management
Connection Pool Configuration
spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.auto-commit=true spring.datasource.hikari.max-lifetime=300000
Known Issues
- •Existing
Programentity may have legacy code or typos - •Table naming: Use singular, lowercase (e.g.,
student, notstudents) - •Field naming in entities should be camelCase (e.g.,
firstName, notfirst_name)
When to Use This Skill
Claude will automatically reference this skill when:
- •Generating technical design documents (
/generate-design) - •Implementing new features (
/implement-database,/implement-service,/implement-api) - •Reviewing code for consistency
- •Creating new entities, services, controllers, or repositories
- •Planning database migrations
- •Setting up error handling patterns
Example: Creating a New Feature (Course Management)
When implementing a new "Course" feature:
- •
Entity:
Course.java(singular)- •Package:
com.example.hellospringboot.entity - •Table:
@Table(name="course")(singular, lowercase) - •Lombok:
@Data,@NoArgsConstructor,@AllArgsConstructor
- •Package:
- •
Repository:
CourseRepository.java- •Package:
com.example.hellospringboot.repository - •Extends:
JpaRepository<Course, Long>
- •Package:
- •
Service:
CourseService.java(interface) +CourseServiceImpl.java- •Package:
com.example.hellospringboot.service - •Annotations:
@Service,@Slf4j,@RequiredArgsConstructor - •Use
@Transactionalappropriately
- •Package:
- •
Controller:
CourseController.java- •Package:
com.example.hellospringboot.controller - •Annotations:
@RestController,@RequestMapping("/api/courses") - •Lombok:
@RequiredArgsConstructor,@Slf4j
- •Package:
- •
DTOs:
CourseResponse.java,CreateCourseRequest.java- •Package:
com.example.hellospringboot.dto
- •Package:
- •
Exceptions:
CourseNotFoundException.java- •Package:
com.example.hellospringboot.exception
- •Package:
- •
Migration:
V{n}__Add_course_table.sql- •Location:
src/main/resources/db/migration/
- •Location:
Summary
This skill ensures that all generated code and documentation follows:
- •✅ Spring Boot 3.4.4 + Java 17
- •✅ PostgreSQL 15 with Flyway migrations
- •✅ Layer-based package structure
- •✅ Consistent naming conventions
- •✅ Lombok for boilerplate reduction
- •✅ Constructor injection with
@RequiredArgsConstructor - •✅ Proper transaction management
- •✅ Comprehensive logging with
@Slf4j - •✅ Global exception handling
- •✅ Proper HTTP status codes