{"slug": "i-built-an-ai-meeting-summarizer-with-spring-ai-here-s-how-and-why", "title": "I Built an AI Meeting Summarizer with Spring AI — Here's How (and Why)", "summary": "A developer built an AI meeting summarizer using Java and Spring AI, integrating OpenAI's Whisper for transcription, AssemblyAI for speaker detection, and GPT-4o-mini for generating structured summaries. The tool accepts audio files and outputs meeting titles, overviews, key decisions, and action items.", "body_md": "🚀 The Problem\n\nWait, what did we decide in that meeting?\n\nWe've all been there. You have a productive meeting, everyone agrees on action items, and by the next day, nobody remembers who was supposed to do what.\n\nI wanted to fix this. So I built a tool that:\n\nThe best part? It works with any audio file — upload an MP3, and get a structured summary in seconds.\n\n**And I built it entirely with Java and Spring AI.**\n\n🛠️ **The Tech Stack**\n\n```\nComponent           Technology          Purpose\nFramework           Spring Boot 3.4.x   Application backbone\nAI Integration      Spring AI 1.0.3     Unified AI abstraction\nTranscription       OpenAI Whisper      Speech-to-text\nSpeaker Detection   AssemblyAI          Speaker diarization\nSummary Generation  GPT-4o-mini         LLM-powered summarization\n```\n\n**Why this stack?** Spring AI is the killer feature. It provides a clean, Spring-native way to interact with AI models. No boilerplate HTTP clients. No manual JSON parsing.\n\n🧠 **How It Works**\n\n**Step 1**: Transcribe with Whisper\n\nSpring AI provides OpenAiAudioTranscriptionModel. I inject it directly into my service:\n\n```\n@Service\npublic class TranscriptionService {\n\n    private final OpenAiAudioTranscriptionModel transcriptionModel;\n\n    @Autowired\n    public TranscriptionService(OpenAiAudioTranscriptionModel transcriptionModel) {\n        this.transcriptionModel = transcriptionModel;\n    }\n\n    public String transcribe(File audioFile) {\n        AudioTranscriptionPrompt prompt = new AudioTranscriptionPrompt(\n            new FileSystemResource(audioFile),\n            AudioTranscriptionPrompt.builder()\n                .withModel(\"whisper-1\")\n                .withResponseFormat(TranscriptResponseFormat.JSON)\n                .withLanguage(\"en\")\n                .build()\n        );\n\n        AudioTranscriptionResponse response = transcriptionModel.call(prompt);\n        return response.getResult().getOutput();\n    }\n}\n```\n\n**Step 2**: Detect Speakers with AssemblyAI\n\nSpring AI doesn't have built-in speaker diarization, so I integrated AssemblyAI for this.\n\n```\n@Service\npublic class SpeakerDetectionService {\n\n    private String uploadAudio(File audioFile) throws IOException {\n        // Upload to AssemblyAI\n        // Returns upload_url\n    }\n\n    private String submitTranscription(String audioUrl) {\n        // Submit with speaker_labels: true\n        // Returns transcript ID\n    }\n\n    private List<SpeakerUtterance> getTranscriptWithSpeakers(String transcriptId) {\n        // Poll until status is \"completed\"\n        // Returns utterances with speaker labels\n    }\n}\n```\n\nWhy this matters: Speaker detection turns a plain transcript into an actionable meeting summary. Without it, you can't assign tasks to specific people.\n\n**Step 3**: Generate the Summary with GPT\n\nThis is where the magic happens. I use Spring AI's ChatClient with a custom prompt:\n\n```\n@Service\npublic class SummaryService {\n\n    private final ChatClient chatClient;\n\n    public String generateSummary(String transcript, List<SpeakerUtterance> utterances) {\n        String prompt = buildPrompt(transcript, utterances);\n\n        return chatClient.prompt()\n            .user(prompt)\n            .call()\n            .content();\n    }\n\n    private String buildPrompt(String transcript, List<SpeakerUtterance> utterances) {\n        return \"\"\"\n            You are a professional meeting summarizer with 10 years of experience.\n\n            IMPORTANT: First, generate a concise, professional meeting title (max 5 words).\n\n            Provide your response in this exact format:\n\n            # MEETING TITLE\n            [Your concise title here, max 5 words]\n\n            # MEETING OVERVIEW\n            One paragraph summary (max 50 words)\n\n            # KEY DECISIONS\n            - Decision 1\n            - Decision 2\n\n            # ACTION ITEMS\n            | Task | Assignee | Deadline | Priority |\n            |------|----------|----------|----------|\n            | [Task] | [Person] | [Date] | [HIGH/MEDIUM/LOW] |\n\n            # UNRESOLVED ISSUES\n            - Issue 1\n\n            # NEXT STEPS\n            - Step 1\n\n            Rules:\n            1. ONLY use information from the transcript\n            2. If a deadline or assignee is missing, write \"Not specified\"\n            3. Keep action items to a maximum of 5\n            4. Be concise and actionable\n            \"\"\".formatted(transcript, speakerInfo);\n    }\n}\n```\n\n💡 Pro tip: Never hardcode your prompt directly in the service. Store it as a classpath resource and load it with @Value. This way you can tweak the output without a code review.\n\n📡 **The Controller**\n\nThe REST endpoint ties everything together:\n\n```\n@PostMapping(\"/process\")\npublic ResponseEntity<Map<String, Object>> processMeeting(\n        @RequestParam(\"audio\") MultipartFile audioFile) {\n\n    // 1. Save uploaded file to temp location\n    File tempFile = saveToTemp(audioFile);\n\n    // 2. Transcribe with Whisper\n    String transcript = transcriptionService.transcribe(tempFile);\n\n    // 3. Detect speakers with AssemblyAI\n    List<SpeakerUtterance> utterances = speakerDetectionService.detectSpeakers(tempFile);\n\n    // 4. Generate summary with GPT\n    String summary = summaryService.generateSummary(transcript, utterances);\n\n    // 5. Parse tasks from the summary\n    List<Map<String, String>> tasks = parseTasks(summary);\n\n    // 6. Return structured JSON\n    return ResponseEntity.ok(buildResponse(summary, utterances, tasks));\n}\n```\n\n📊 **What the Output Looks Like**\n\nHere's a real response from a test meeting:\n\n```\n{\n  \"meetingTitle\": \"Student Attendance Concerns\",\n  \"meetingOverview\": \"The meeting addressed a decline in student attendance on Fridays...\",\n  \"keyDecisions\": [\n    \"Host a pancake breakfast to encourage attendance.\",\n    \"Put up health posters to address seasonal illness.\"\n  ],\n  \"tasks\": [\n    {\n      \"description\": \"Organize pancake breakfast\",\n      \"assignee\": \"Not specified\",\n      \"deadline\": \"Not specified\",\n      \"priority\": \"HIGH\"\n    },\n    {\n      \"description\": \"Create and display health posters\",\n      \"assignee\": \"Not specified\",\n      \"deadline\": \"Not specified\",\n      \"priority\": \"MEDIUM\"\n    }\n  ],\n  \"speakerCount\": 3\n}\n```\n\n🚀 **Try It Yourself**\n\nThe API is live:\n\n[meeting-summarizer](https://meeting-summarizer-pj7h.onrender.com/)\n\n💡 **What I Learned**\n\n**1. Spring AI Eliminates Boilerplate**\n\nI didn't write a single HTTP client or JSON parser. The ChatClient abstraction handles everything. You can swap the LLM provider by changing one dependency and one properties line.\n\n**2. Prompt Engineering Is the Real Skill**\n\nThe output quality depends entirely on the prompt. I iterated on my prompt more than on any code component. A good prompt with system context makes the difference between generic and actionable output.\n\n**3. File-Based Processing Has Privacy Benefits**\n\nUnlike bot-based tools (like Fireflies or Otter), my tool processes uploaded files — no one needs to join your meetings. There's growing demand for privacy-first meeting assistants.\n\n**4. Speaker Detection Changes Everything**\n\nWithout speaker labels, you just get a wall of text. With them, you get clear ownership of action items.\n\n🔮 **What's Next**\n\n📚 **Resources**\n\n[Spring AI Documentation](https://spring.io/projects/spring-ai)\n\n[AssemblyAI Speaker Diarization](https://www.assemblyai.com/docs/pre-recorded-audio/label-speakers)\n\n[OpenAI Whisper API](https://developers.openai.com/api/docs/guides/speech-to-text)\n\n[GitHub Repository](https://github.com/leosarabi/meeting-summarizer)\n\n💬 **Let's Connect**\n\nIf you're building AI tools with Spring AI, I'd love to hear about your experience. Drop a comment.\n\nThanks for reading! 🚀", "url": "https://wpnews.pro/news/i-built-an-ai-meeting-summarizer-with-spring-ai-here-s-how-and-why", "canonical_source": "https://dev.to/leo_sarabi_b1777c68947678/i-built-an-ai-meeting-summarizer-with-spring-ai-heres-how-and-why-38nh", "published_at": "2026-08-29 08:50:19+00:00", "updated_at": "2026-08-29 09:19:00.820755+00:00", "lang": "en", "topics": ["artificial-intelligence", "developer-tools", "natural-language-processing"], "entities": ["Spring AI", "OpenAI", "Whisper", "AssemblyAI", "GPT-4o-mini", "Java", "Spring Boot"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-ai-meeting-summarizer-with-spring-ai-here-s-how-and-why", "markdown": "https://wpnews.pro/news/i-built-an-ai-meeting-summarizer-with-spring-ai-here-s-how-and-why.md", "text": "https://wpnews.pro/news/i-built-an-ai-meeting-summarizer-with-spring-ai-here-s-how-and-why.txt", "jsonld": "https://wpnews.pro/news/i-built-an-ai-meeting-summarizer-with-spring-ai-here-s-how-and-why.jsonld"}}