| name | tech-stack-config |
| description | Spring Boot 3.4.4 and Java 17 tech stack configuration, package structure conventions, and coding patterns for consistent API development. Use this when generating designs, implementing features, or reviewing code to ensure alignment with the project's established tech stack and conventions. |
| allowed-tools | Read(*), Glob(*), Grep(*) |
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