Build a Full‑Stack Meeting Scheduler with Spring AI Tools and Spring Boot 3
This tutorial demonstrates how to create a complete meeting‑management CRUD application using Spring Boot 3, Spring AI Tools, and a custom ChatClient, covering environment setup, entity definitions, repository, service layer, tool integration, and REST endpoints with code examples and screenshots.
Spring Boot 3 practical case collection has been updated to 122 examples, offering free, permanent updates for subscribers.
1. Introduction to Tools
Tools empower large language models to interact with external APIs or functions, enabling real‑time operations such as weather queries, database access, multi‑step agents for travel planning or e‑commerce ordering, and deep integration with enterprise systems like CRM and ERP.
For more details, see the article "Too powerful! Spring AI calls local functions to fetch real‑time data".
2. Practical Example: Meeting Scheduler
We will implement a full‑chain CRUD module for meeting appointments using Spring AI + Tools. Below is a sample query to retrieve all meetings.
Next, we will implement the query function and other operations (add, delete, etc.).
2.1 Basic CRUD
Entity Objects
<code><Entity>
<Table name="t_meeting">
public class Meeting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String description;
private LocalDateTime startTime;
private Integer duration;
@Enumerated(EnumType.STRING)
private Urgency urgency;
private String creator;
@OneToMany(mappedBy = "meeting", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Participant> participants = new HashSet<>();
}
@Entity
@Table(name = "t_participant")
public class Participant {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(optional = false)
@JoinColumn(name = "mid")
@JsonIgnore
private Meeting meeting;
}</code>Repository Definition
<code>public interface MeetingRepository extends JpaRepository<Meeting, Long> { }</code>Service CRUD Operations
<code>@Service
public class MeetingService {
private final MeetingRepository meetingRepository;
public MeetingService(MeetingRepository meetingRepository) { this.meetingRepository = meetingRepository; }
@Transactional
public Meeting createMeeting(Meeting meeting) { return meetingRepository.save(meeting); }
public Meeting getMeetingById(Long id) { return meetingRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Meeting not found")); }
@Transactional
public void deleteMeeting(Long id) { meetingRepository.deleteById(id); }
@Transactional(readOnly = true)
public List<Meeting> getAllMeetings() { return meetingRepository.findAll(); }
}</code>2.2 Tools Definition
First, define a VO object to receive parsed user input.
<code>public class MeetingVO {
@ToolParam(description = "Meeting title")
private String title;
@ToolParam(description = "Meeting description")
private String description;
@ToolParam(description = "Meeting time")
private LocalDateTime startTime;
@ToolParam(description = "Duration in minutes")
private Integer duration;
@ToolParam(description = "Urgency level: LOW, MEDIUM, HIGH")
@Enumerated(EnumType.STRING)
private Urgency urgency;
@ToolParam(description = "Creator")
private String creator;
@ToolParam(description = "Participants, comma‑separated")
private Set<String> participants = new HashSet<>();
// getters, setters
}</code>Tool implementation:
<code>@Component
public class MeetingTools {
private final MeetingService meetingService;
public MeetingTools(MeetingService meetingService) { this.meetingService = meetingService; }
@Tool(description = "Add a meeting")
public R<Meeting> addMeeting(MeetingVO meetingVO) {
Meeting meeting = new Meeting();
BeanUtils.copyProperties(meetingVO, meeting);
Set<Participant> participants = meetingVO.getParticipants().stream()
.map(p -> new Participant(p, meeting))
.collect(Collectors.toSet());
meeting.setParticipants(participants);
meetingService.createMeeting(meeting);
return R.success(meeting);
}
@Tool(description = "Delete a meeting")
public R<String> deleteMeeting(@ToolParam(description = "Meeting ID") Long id) {
meetingService.deleteMeeting(id);
return R.success("Deleted meeting [" + id + "] successfully");
}
@Tool(description = "Query a meeting by ID")
public R<Meeting> queryMeeting(@ToolParam(description = "Meeting ID") Long id) {
return R.success(meetingService.getMeetingById(id));
}
@Tool(description = "Query all meetings")
public R<List<Meeting>> queryMeetings() {
return R.success(meetingService.getAllMeetings());
}
}</code>2.3 ChatClient Configuration
<code>@Configuration
public class ChatConfig {
@Bean
ChatClient meetingChat(ChatClient.Builder chatClientBuilder) {
String systemMessage = """
Current time: {date}. Output results using an HTML table, responsive width, font size 12px. No other content.
""";
return chatClientBuilder.defaultSystem(systemMessage).build();
}
}</code>2.4 Test Controller
<code>@RestController
@RequestMapping("/meeting/chat")
public class MeetingChatController {
private final MeetingTools meetingTools;
private final ChatClient chatClient;
public MeetingChatController(MeetingTools meetingTools, ChatClient chatClient) {
this.meetingTools = meetingTools;
this.chatClient = chatClient;
}
@GetMapping
public ResponseEntity<?> chat(String message) {
Prompt prompt = new Prompt(message);
String content = chatClient.prompt(prompt)
.system(p -> p.param("date", new Date()))
.tools(meetingTools)
.call()
.content();
return ResponseEntity.ok(content);
}
}</code>Example request to create a meeting:
http://localhost:8080/meeting/chat?message=Schedule a meeting today at 12:30 for 20 minutes, urgent, topic "P1 incident", participants: Tianqi, Zhao Liu, Pack.
Query a specific meeting:
Query all meetings:
Delete a meeting:
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.