AI assistant guidance for the Interview Simulator project.
AI-Powered Job Interview Simulator using Gemini Live API with real-time bidirectional audio.
Stack: Spring Boot 4.0.0 | Java 25 | PostgreSQL | Thymeleaf | WebSocket/STOMP | OkHttp | Lombok | PDFBox | Apache POI
Features:
- Real-time voice conversation with AI interviewer
- Multi-language support (English/Bulgarian)
- CV/Resume upload and parsing (PDF/DOCX)
- Voice selection (4 interviewer voices)
- DEV/PROD/REVIEWER modes (backend vs user-provided vs multi-key rotation API keys)
- Position-specific and difficulty-aware interviews
- Automatic grading and detailed feedback
These rules are non-negotiable:
| Rule | Example |
|---|---|
| File ends with empty line | All files: .java, .sql, .xml, .html, .js |
| Two empty lines between methods | Includes before first method |
| One empty line between fields | Each field declaration separated |
| Closing brace comments | }//methodName and }//ClassName |
| Tab indentation | Use tabs, not spaces |
| Same-line braces | if (x) { not if (x)\n{ |
@Slf4j
@RequiredArgsConstructor
@Service
public class MyService {
private final MyRepository repository;
public void doWork() {
log.info("Working");
}//doWork
}//MyService| Type | Pattern | Example |
|---|---|---|
| Entity | CamelCase | InterviewSession |
| Table | snake_case | interview_sessions |
| Service Interface | {Name}Service |
InterviewService |
| Service Impl | {Name}ServiceImpl |
InterviewServiceImpl |
| Repository | {Entity}Repository |
InterviewSessionRepository |
| Controller | {Name}Controller |
InterviewWebSocketController |
| Test | {Class}Test |
InterviewServiceTest |
| Migration | V{n}__{desc}.sql |
V1__initial_schema.sql |
${var} in SQL files - Flyway treats it as placeholder!
| Annotation | Use Case |
|---|---|
@Slf4j |
All services/controllers (auto-injects log) |
@RequiredArgsConstructor |
Constructor injection for final fields |
@Data |
Entities (getters/setters/equals/hashCode) |
@Builder |
Fluent object construction |
@Slf4j
@RequiredArgsConstructor
@Service
public class MyService {
private final MyRepository repo; // Auto-injected via constructor
public void work() {
log.info("Working"); // log available via @Slf4j
}//work
}//MyServiceLog these: External API calls, WebSocket events, errors, state changes
Don't log: Simple CRUD, getters/setters, every method entry/exit
| Level | Use |
|---|---|
error |
Exceptions, critical failures |
warn |
Recoverable issues, approaching limits |
info |
Business events (session start/end) |
debug |
Diagnostic details (audio chunks) |
// ❌ log.info("Started");
// ✅ log.info("Started session {} for {}", sessionId, name);// Interface
public interface MyService {
void doWork();
}
// Implementation
@Slf4j
@RequiredArgsConstructor
@Service
public class MyServiceImpl implements MyService {
private final MyRepository repo;
@Override
@Transactional
public void doWork() { }//doWork
}//MyServiceImpl// Custom exception
public class SessionNotFoundException extends RuntimeException {
public SessionNotFoundException(UUID id) {
super("Session not found: " + id);
}
}
// Global handler with @ControllerAdvice@Entity
@Table(name = "my_table")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
}//MyEntity| Client → Server | Purpose |
|---|---|
/app/interview/start |
Start interview session with config |
/app/interview/audio |
Send audio chunk (base64 PCM) |
/app/interview/end |
End interview manually |
/app/interview/mic-off |
Signal mic muted |
| Server → Client | Purpose |
|---|---|
/user/queue/status |
Connection/turn status |
/user/queue/audio |
AI audio response |
/user/queue/transcript |
Speech transcription (user/AI) |
/user/queue/report |
Final grading report |
/user/queue/error |
Error messages (with flags: rateLimited, invalidKey, requiresApiKey) |
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
Redirects to /setup/step1 |
GET |
/setup/step1 |
Setup wizard - Step 1 (Profile) |
GET |
/setup/step2 |
Setup wizard - Step 2 (Details + CV) |
GET |
/setup/step3 |
Setup wizard - Step 3 (Voice + Language) |
GET |
/interview |
Interview page (requires completed setup in session) |
GET |
/report/{sessionId} |
Server-rendered report page |
POST |
/api/cv/upload |
Upload CV (PDF/DOCX), returns extracted text |
GET |
/api/mode |
Get app mode (DEV/PROD/REVIEWER) |
POST |
/api/validate-key |
Validate Gemini API key |
GET |
/api/voices |
Get available voice options |
GET |
/api/voices/preview/{voiceId}/{language} |
Get voice preview WAV |
GET |
/admin/login |
Admin login page |
GET |
/admin/dashboard |
Admin dashboard (requires ROLE_ADMIN) |
POST |
/admin/change-password |
Change admin password |
GET |
/legal/privacy |
Mode-aware Privacy Policy page |
GET |
/legal/terms |
Mode-aware Terms & Conditions page |
GET |
/error/mobile-not-supported |
Mobile device redirect page |
Approach: Server-rendered multi-page wizard with minimal JavaScript
/setup/step1 → /setup/step2 → /setup/step3 → /interview → /report/{id}
- Form data stored in HTTP session via
@SessionAttributes("setupForm") - Persists across wizard steps and language switches
- Cleared after interview starts (one-time use)
- Uses
SessionStatus.setComplete()for proper cleanup
audio-processor.js(603 lines) - WebSocket + audio processing (interview page only)apikey.js(417 lines) - API key onboarding + validation (PROD mode only)interview.js(315 lines) - Interview UI controls (interview page only)microphone-check.js(92 lines) - Microphone availability check (setup pages only)language-switcher.js(73 lines) - Language dropdown (all pages)
Scripts are conditionally loaded via Thymeleaf th:if in bodyBottom.html using model attributes
(appMode, isSetupPage, isInterviewPage) set by controllers. SockJS/STOMP CDN libraries are
also only loaded on the interview page.
mvn clean compile # Build
mvn spring-boot:run # Run (port 8080)
mvn test # All tests
mvn test -Dtest=MyTest # Specific test
mvn flyway:migrate # Apply DB migrationsexport APP_MODE=DEV
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=interview_db
export DB_USERNAME=postgres
export DB_PASSWORD=secret
export GEMINI_API_KEY=AIza...
# REVIEWER mode only:
# export GEMINI_REVIEWER_KEYS=AIza...key1,AIza...key2,AIza...key3
# export GEMINI_GRADING_MODELS=gemini-3-flash-preview,gemini-2.5-flash,gemini-2.5-flash-lite,gemma-3-12b-it| Service | Responsibility |
|---|---|
GeminiIntegrationService |
Session lifecycle, WebSocket routing, reconnection |
GeminiLiveClient |
Low-level WebSocket to Gemini API |
GeminiModelRotationService |
Model/key rotation with error-based rate limit tracking |
InterviewService |
Database CRUD for sessions |
GradingService |
AI-powered evaluation after interview (with model fallback) |
InterviewPromptService |
Language/difficulty/position-aware prompts |
CvProcessingService |
PDF/DOCX text extraction (in-memory only, files are never stored) |
AdminService |
Dashboard stats, session browsing, password management |
AdminUserDetailsService |
Spring Security user details for admin authentication |
InputSanitizerService |
Input validation & sanitization |
RateLimitService |
API key validation rate limiting |
Message files: messages.properties, messages_bg.properties, messages_en.properties
# Format: section.subsection.element.attribute
setup.step1.candidateName=Candidate Name
setup.step1.candidateName.placeholder=e.g., John Doe
report.verdict.strongHire=STRONG HIRE
apikey.modal.title=Gemini API Key Required<span th:text="#{setup.title}" />
<input th:placeholder="#{setup.step1.candidateName.placeholder}" />- Add key to
messages.properties(English) - Add Bulgarian translation to
messages_bg.properties - Use
#{key.name}in Thymeleaf templates
- File ends with empty line
- Two empty lines between methods
- Closing brace comments (
}//name) -
@Slf4jinstead of manual logger - Contextual log messages
- No secrets in logs
- Custom exceptions used
- Tests written
- Flyway migration for DB changes
- i18n messages for new UI text
- Both English and Bulgarian translations added
For complete documentation, see the docs/ folder:
| Document | Description |
|---|---|
| README.md | Project overview and quick start |
| docs/SETUP.md | Local development setup |
| docs/DOCKER.md | Docker deployment guide |
| docs/ARCHITECTURE.md | System architecture |
| docs/API.md | REST and WebSocket API reference |
| docs/CONTRIBUTING.md | Contribution guidelines |