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 validationResponseEntityfor 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.sqlV2__Add_program_table.sqlV3__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
Scan to join WeChat group