Skip to main content

haft generate

Generate boilerplate code for Spring Boot applications.

Usage

haft generate <subcommand> [name] [flags]
haft g <subcommand> [name] [flags] # alias

Subcommands

CommandAliasDescription
haft generate resourcehaft g rGenerate complete CRUD resource (9 files)
haft generate controllerhaft g coGenerate REST controller
haft generate servicehaft g sGenerate service interface + implementation
haft generate repositoryhaft g repoGenerate JPA repository interface
haft generate entityhaft g eGenerate JPA entity class
haft generate dto-Generate Request and Response DTOs
haft generate exceptionhaft g exGenerate global exception handler
haft generate confighaft g cfgGenerate configuration classes
haft generate securityhaft g secGenerate security configuration (JWT, Session, OAuth2)

Smart Detection

Haft reads your build file (pom.xml or build.gradle) to automatically detect and customize generated code:

DependencyDetectionEffect
Lombokorg.projectlombok:lombokGenerates @Getter, @Setter, @Builder, etc.
Spring Data JPAspring-boot-starter-data-jpaGenerates Entity and Repository with @Transactional
Validationspring-boot-starter-validationAdds @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

ArchitectureDescriptionFile Placement
LayeredTraditional layers (controller, service, repository)src/main/java/com/example/controller/UserController.java
FeatureFeature-based packages (user, product, order)src/main/java/com/example/user/UserController.java
HexagonalPorts & adapters patternsrc/main/java/com/example/adapter/in/web/UserController.java
CleanClean architecture with use casessrc/main/java/com/example/infrastructure/web/UserController.java
ModularMulti-module monolithuser-module/src/main/java/.../UserController.java
FlatSimple flat structuresrc/main/java/com/example/UserController.java

Full Detection Capabilities

DetectionOptionsEffect
ArchitectureLayered, Feature, Hexagonal, Clean, Modular, FlatFiles placed in correct location
Feature StyleFlat vs Nesteduser/UserController.java vs user/controller/UserController.java
DTO NamingRequest/Response vs DTOUserRequest.java vs UserDTO.java
ID TypeLong vs UUIDprivate Long id vs private UUID id
MapperMapStruct, ModelMapper, ManualGenerates appropriate mapper type
Base EntityDetects BaseEntity or AbstractEntityExtends your base class
Lombok@Data, @Builder, @NoArgsConstructorUses detected annotations
SwaggerOpenAPI v3 vs Swagger v2Correct annotations
ValidationJakarta vs JavaxCorrect import packages
DatabaseJPA, MongoDB, Cassandra, R2DBCGenerates 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 --refresh flag 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

FileDescription
controller/UserController.javaREST controller with CRUD endpoints
service/UserService.javaService interface
service/impl/UserServiceImpl.javaService implementation
repository/UserRepository.javaSpring Data JPA repository
entity/User.javaJPA entity
dto/UserRequest.javaRequest DTO
dto/UserResponse.javaResponse DTO
mapper/UserMapper.javaEntity to DTO mapper
exception/ResourceNotFoundException.javaShared exception (created once)

Test Files (generated by default)

FileDescription
UserServiceTest.javaUnit tests with Mockito
UserControllerTest.javaIntegration tests with MockMvc
UserRepositoryTest.javaIntegration tests with @DataJpaTest
UserEntityTest.javaUnit tests for entity

Flags

FlagShortDescription
--package-pOverride base package (auto-detected from build file)
--no-interactiveSkip interactive wizard
--skip-entitySkip entity generation
--skip-repositorySkip repository generation
--skip-testsSkip test file generation
--legacyUse legacy layered generation (ignores architecture detection)
--refreshForce re-scan project (ignore cached profile)
--jsonOutput 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

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard
--jsonOutput 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

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard
--jsonOutput 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

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard
--jsonOutput 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> {
}
note

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

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard
--jsonOutput 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;
}
}
note

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

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard
--request-onlyGenerate only Request DTO
--response-onlyGenerate only Response DTO
--jsonOutput 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):

FileStatus CodeDescription
GlobalExceptionHandler.java-Central exception handler with @ControllerAdvice
ErrorResponse.java-Standardized error response DTO
ResourceNotFoundException.java404Resource not found
BadRequestException.java400Bad request / invalid input
UnauthorizedException.java401Authentication required
ForbiddenException.java403Access denied

Optional exceptions (select via interactive picker):

FileStatus CodeDescription
ConflictException.java409Resource already exists
MethodNotAllowedException.java405HTTP method not supported
GoneException.java410Resource no longer available
UnsupportedMediaTypeException.java415Wrong content type
UnprocessableEntityException.java422Semantic errors in request
TooManyRequestsException.java429Rate limiting
InternalServerErrorException.java500Explicit server error handling
ServiceUnavailableException.java503Service temporarily down
GatewayTimeoutException.java504Upstream timeout

Flags

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard (default exceptions only)
--allInclude all optional exceptions
--refreshForce re-scan project (ignore cached profile)
--jsonOutput result as JSON

File Placement by Architecture

ArchitecturePackage Location
Layeredcom.example.exception
Featurecom.example.common.exception
Hexagonalcom.example.infrastructure.exception
Cleancom.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

FileDescription
CorsConfig.javaCross-origin resource sharing setup
OpenApiConfig.javaSwagger/OpenAPI 3.0 documentation
JacksonConfig.javaJSON serialization settings
AsyncConfig.javaAsync/thread pool configuration
CacheConfig.javaSpring Cache configuration
AuditingConfig.javaJPA auditing (@CreatedDate, @LastModifiedDate)
WebMvcConfig.javaWeb MVC customization (resource handlers)

Flags

FlagShortDescription
--package-pOverride base package
--no-interactiveSkip interactive wizard (requires --all)
--allGenerate all configuration classes
--refreshForce re-scan project (ignore cached profile)
--jsonOutput result as JSON

File Placement by Architecture

ArchitecturePackage Location
Layeredcom.example.config
Featurecom.example.common.config
Hexagonalcom.example.infrastructure.config
Cleancom.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");
}
}
tip

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:

  • userUser
  • user-profileUserProfile
  • user_accountUserAccount

See Also