haft generate
Generate boilerplate code for Spring Boot applications.
Usage
haft generate <subcommand> [name] [flags]
haft g <subcommand> [name] [flags] # alias
Subcommands
| Command | Alias | Description |
|---|---|---|
haft generate resource | haft g r | Generate complete CRUD resource (9 files) |
haft generate controller | haft g co | Generate REST controller |
haft generate service | haft g s | Generate service interface + implementation |
haft generate repository | haft g repo | Generate JPA repository interface |
haft generate entity | haft g e | Generate JPA entity class |
haft generate dto | - | Generate Request and Response DTOs |
haft generate exception | haft g ex | Generate global exception handler |
haft generate config | haft g cfg | Generate configuration classes |
haft generate security | haft g sec | Generate security configuration (JWT, Session, OAuth2) |
Smart Detection
Haft reads your build file (pom.xml or build.gradle) to automatically detect and customize generated code:
| Dependency | Detection | Effect |
|---|---|---|
| Lombok | org.projectlombok:lombok | Generates @Getter, @Setter, @Builder, etc. |
| Spring Data JPA | spring-boot-starter-data-jpa | Generates Entity and Repository with @Transactional |
| Validation | spring-boot-starter-validation | Adds @Valid to controller parameters |
Intelligent Architecture Detection
Haft's detection engine scans your project and learns from your existing patterns. The first scan is cached for instant subsequent runs.
Detected Architectures
| Architecture | Description | File Placement |
|---|---|---|
| Layered | Traditional layers (controller, service, repository) | src/main/java/com/example/controller/UserController.java |
| Feature | Feature-based packages (user, product, order) | src/main/java/com/example/user/UserController.java |
| Hexagonal | Ports & adapters pattern | src/main/java/com/example/adapter/in/web/UserController.java |
| Clean | Clean architecture with use cases | src/main/java/com/example/infrastructure/web/UserController.java |
| Modular | Multi-module monolith | user-module/src/main/java/.../UserController.java |
| Flat | Simple flat structure | src/main/java/com/example/UserController.java |
Full Detection Capabilities
| Detection | Options | Effect |
|---|---|---|
| Architecture | Layered, Feature, Hexagonal, Clean, Modular, Flat | Files placed in correct location |
| Feature Style | Flat vs Nested | user/UserController.java vs user/controller/UserController.java |
| DTO Naming | Request/Response vs DTO | UserRequest.java vs UserDTO.java |
| ID Type | Long vs UUID | private Long id vs private UUID id |
| Mapper | MapStruct, ModelMapper, Manual | Generates appropriate mapper type |
| Base Entity | Detects BaseEntity or AbstractEntity | Extends your base class |
| Lombok | @Data, @Builder, @NoArgsConstructor | Uses detected annotations |
| Swagger | OpenAPI v3 vs Swagger v2 | Correct annotations |
| Validation | Jakarta vs Javax | Correct import packages |
| Database | JPA, MongoDB, Cassandra, R2DBC | Generates appropriate repository |
Profile Caching
Haft caches the detected project profile for instant subsequent runs:
.haft/
├── profile.yaml # Cached detection results
└── checksum # Source file checksum for invalidation
- First run: Scans your project and saves the profile
- Subsequent runs: Uses cached profile (instant!)
- Auto-invalidation: Re-scans when source files change or after 24 hours
- Manual refresh: Use
--refreshflag to force re-scan
haft generate resource
Generate a complete CRUD resource with all layers.
# Interactive mode (recommended)
haft generate resource
# With resource name
haft generate resource User
haft g r Product
Generated Files
| File | Description |
|---|---|
controller/UserController.java | REST controller with CRUD endpoints |
service/UserService.java | Service interface |
service/impl/UserServiceImpl.java | Service implementation |
repository/UserRepository.java | Spring Data JPA repository |
entity/User.java | JPA entity |
dto/UserRequest.java | Request DTO |
dto/UserResponse.java | Response DTO |
mapper/UserMapper.java | Entity to DTO mapper |
exception/ResourceNotFoundException.java | Shared exception (created once) |
Test Files (generated by default)
| File | Description |
|---|---|
UserServiceTest.java | Unit tests with Mockito |
UserControllerTest.java | Integration tests with MockMvc |
UserRepositoryTest.java | Integration tests with @DataJpaTest |
UserEntityTest.java | Unit tests for entity |
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package (auto-detected from build file) |
--no-interactive | Skip interactive wizard | |
--skip-entity | Skip entity generation | |
--skip-repository | Skip repository generation | |
--skip-tests | Skip test file generation | |
--legacy | Use legacy layered generation (ignores architecture detection) | |
--refresh | Force re-scan project (ignore cached profile) | |
--json | Output result as JSON |
Examples
# Interactive mode - wizard guides you through options
haft generate resource
# Non-interactive with name
haft generate resource User --no-interactive
# Override base package
haft generate resource Product --package com.mycompany.store
# Skip database layer (service-only pattern)
haft generate resource Payment --skip-entity --skip-repository
# Skip test generation
haft generate resource Order --skip-tests
# Force re-scan project profile
haft generate resource Customer --refresh
# Use legacy layered generation (ignores detected architecture)
haft generate resource Invoice --legacy
haft generate controller
Generate a REST controller with CRUD endpoints.
# Interactive mode
haft generate controller
# With controller name
haft generate controller User
haft g co Product
Generated File
controller/UserController.java
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard | |
--json | Output result as JSON |
Example Output
package com.example.demo.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import com.example.demo.service.UserService;
import com.example.demo.dto.UserRequest;
import com.example.demo.dto.UserResponse;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<List<UserResponse>> getAll() {
return ResponseEntity.ok(userService.findAll());
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getById(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@PostMapping
public ResponseEntity<UserResponse> create(@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok(userService.create(request));
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> update(@PathVariable Long id, @Valid @RequestBody UserRequest request) {
return ResponseEntity.ok(userService.update(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
haft generate service
Generate a service interface and implementation.
# Interactive mode
haft generate service
# With service name
haft generate service User
haft g s Product
Generated Files
service/UserService.java
service/impl/UserServiceImpl.java
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard | |
--json | Output result as JSON |
Example Output (Interface)
package com.example.demo.service;
import com.example.demo.dto.UserRequest;
import com.example.demo.dto.UserResponse;
import java.util.List;
public interface UserService {
List<UserResponse> findAll();
UserResponse findById(Long id);
UserResponse create(UserRequest request);
UserResponse update(Long id, UserRequest request);
void delete(Long id);
}
Example Output (Implementation)
package com.example.demo.service.impl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.service.UserService;
import com.example.demo.dto.UserRequest;
import com.example.demo.dto.UserResponse;
import java.util.List;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Override
@Transactional(readOnly = true)
public List<UserResponse> findAll() {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
@Transactional(readOnly = true)
public UserResponse findById(Long id) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public UserResponse create(UserRequest request) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public UserResponse update(Long id, UserRequest request) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void delete(Long id) {
throw new UnsupportedOperationException("Not implemented yet");
}
}
haft generate repository
Generate a Spring Data JPA repository interface.
# Interactive mode
haft generate repository
# With repository name
haft generate repository User
haft g repo Product
Generated File
repository/UserRepository.java
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard | |
--json | Output result as JSON |
Example Output
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Repository generation requires Spring Data JPA dependency in your project.
haft generate entity
Generate a JPA entity class.
# Interactive mode
haft generate entity
# With entity name
haft generate entity User
haft g e Product
Generated File
entity/User.java
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard | |
--json | Output result as JSON |
Example Output (with Lombok)
package com.example.demo.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
Example Output (without Lombok)
package com.example.demo.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Entity generation requires Spring Data JPA dependency in your project.
haft generate dto
Generate Request and Response DTO classes.
# Interactive mode
haft generate dto
# With DTO name
haft generate dto User
haft generate dto Product
# Generate only Request DTO
haft generate dto User --request-only
# Generate only Response DTO
haft generate dto User --response-only
Generated Files
dto/UserRequest.java
dto/UserResponse.java
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard | |
--request-only | Generate only Request DTO | |
--response-only | Generate only Response DTO | |
--json | Output result as JSON |
Example Output (Request DTO with Lombok + Validation)
package com.example.demo.dto;
import lombok.*;
import jakarta.validation.constraints.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserRequest {
}
Example Output (Response DTO with Lombok)
package com.example.demo.dto;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserResponse {
}
haft generate exception
Generate a global exception handler with @ControllerAdvice.
# Interactive mode with optional exception picker
haft generate exception
haft g ex
# Generate with all optional exceptions
haft generate exception --all
# Non-interactive mode (default exceptions only)
haft generate exception --no-interactive
# Override base package
haft generate exception --package com.example.app
Generated Files
Default exceptions (always generated):
| File | Status Code | Description |
|---|---|---|
GlobalExceptionHandler.java | - | Central exception handler with @ControllerAdvice |
ErrorResponse.java | - | Standardized error response DTO |
ResourceNotFoundException.java | 404 | Resource not found |
BadRequestException.java | 400 | Bad request / invalid input |
UnauthorizedException.java | 401 | Authentication required |
ForbiddenException.java | 403 | Access denied |
Optional exceptions (select via interactive picker):
| File | Status Code | Description |
|---|---|---|
ConflictException.java | 409 | Resource already exists |
MethodNotAllowedException.java | 405 | HTTP method not supported |
GoneException.java | 410 | Resource no longer available |
UnsupportedMediaTypeException.java | 415 | Wrong content type |
UnprocessableEntityException.java | 422 | Semantic errors in request |
TooManyRequestsException.java | 429 | Rate limiting |
InternalServerErrorException.java | 500 | Explicit server error handling |
ServiceUnavailableException.java | 503 | Service temporarily down |
GatewayTimeoutException.java | 504 | Upstream timeout |
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard (default exceptions only) | |
--all | Include all optional exceptions | |
--refresh | Force re-scan project (ignore cached profile) | |
--json | Output result as JSON |
File Placement by Architecture
| Architecture | Package Location |
|---|---|
| Layered | com.example.exception |
| Feature | com.example.common.exception |
| Hexagonal | com.example.infrastructure.exception |
| Clean | com.example.infrastructure.exception |
Example Output (GlobalExceptionHandler.java)
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.bind.MethodArgumentNotValidException;
import java.time.LocalDateTime;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
request.getDescription(false),
LocalDateTime.now()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(
BadRequestException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
ex.getMessage(),
request.getDescription(false),
LocalDateTime.now()
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
// ... more handlers
}
Example Usage in Service
@Service
public class UserServiceImpl implements UserService {
@Override
public UserResponse findById(Long id) {
return userRepository.findById(id)
.map(userMapper::toResponse)
.orElseThrow(() -> new ResourceNotFoundException("User", "id", id));
}
@Override
public UserResponse create(UserRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new ConflictException("User", "email", request.getEmail());
}
// ... create user
}
}
haft generate config
Generate common Spring Boot configuration classes.
# Interactive mode with configuration picker
haft generate config
haft g cfg
# Generate all configurations
haft generate config --all
# Non-interactive mode (requires --all)
haft generate config --all --no-interactive
# Override base package
haft generate config --package com.example.app
Available Configurations
| File | Description |
|---|---|
CorsConfig.java | Cross-origin resource sharing setup |
OpenApiConfig.java | Swagger/OpenAPI 3.0 documentation |
JacksonConfig.java | JSON serialization settings |
AsyncConfig.java | Async/thread pool configuration |
CacheConfig.java | Spring Cache configuration |
AuditingConfig.java | JPA auditing (@CreatedDate, @LastModifiedDate) |
WebMvcConfig.java | Web MVC customization (resource handlers) |
Flags
| Flag | Short | Description |
|---|---|---|
--package | -p | Override base package |
--no-interactive | Skip interactive wizard (requires --all) | |
--all | Generate all configuration classes | |
--refresh | Force re-scan project (ignore cached profile) | |
--json | Output result as JSON |
File Placement by Architecture
| Architecture | Package Location |
|---|---|
| Layered | com.example.config |
| Feature | com.example.common.config |
| Hexagonal | com.example.infrastructure.config |
| Clean | com.example.infrastructure.config |
Example Output (CorsConfig.java)
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
import java.util.List;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:4200"));
config.setAllowedHeaders(Arrays.asList(
"Origin", "Content-Type", "Accept", "Authorization",
"X-Requested-With", "Access-Control-Request-Method",
"Access-Control-Request-Headers"
));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
Example Output (OpenApiConfig.java)
package com.example.demo.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class OpenApiConfig {
@Value("${spring.application.name:Demo}")
private String applicationName;
@Bean
public OpenAPI customOpenAPI() {
Server devServer = new Server();
devServer.setUrl("http://localhost:8080");
devServer.setDescription("Development server");
Info info = new Info()
.title(applicationName + " API")
.version("1.0.0")
.description("API documentation for " + applicationName);
return new OpenAPI()
.info(info)
.servers(List.of(devServer));
}
}
Example Output (AsyncConfig.java)
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("Async-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
Example Output (AuditingConfig.java)
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import java.util.Optional;
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditingConfig {
@Bean
public AuditorAware<String> auditorProvider() {
// TODO: Return current user from SecurityContext
return () -> Optional.of("system");
}
}
Use AuditingConfig with @CreatedDate, @LastModifiedDate, @CreatedBy, and @LastModifiedBy annotations on your entities:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
private String createdBy;
}
File Safety
Haft never overwrites existing files. If a file already exists, it will be skipped with a warning:
WARN ⚠ Skipped (already exists) file=src/main/java/.../UserController.java
This allows you to safely re-run commands without losing custom code or interrupting your workflow.
Name Validation
Component names must:
- Start with a letter (a-z, A-Z)
- Contain only letters and numbers
- Be at least 2 characters long
Names are automatically converted to PascalCase:
user→Useruser-profile→UserProfileuser_account→UserAccount
See Also
- haft init - Initialize a new project
- haft add - Add dependencies
- haft template - Customize templates
- haft generate security - Security configuration
- Project Structure - Where files are generated