Backend Development 9 min read

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 Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Build a Full‑Stack Meeting Scheduler with Spring AI Tools and Spring Boot 3

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>&lt;Entity&gt;
&lt;Table name="t_meeting"&gt;
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&lt;Participant&gt; participants = new HashSet&lt;&gt;();
}

@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&lt;Meeting, Long&gt; { }</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 BootCRUDSpring AI
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.