{"record":{"id":"440beee58fe07947","repo":"Kareadita/Kavita","slug":"reading-list-title-required","errorCode":"reading-list-title-required","errorMessage":"reading-list-title-required","messagePattern":"reading-list-title-required","errorType":"exception","errorClass":"KavitaException","httpStatus":400,"severity":"error","filePath":"Kavita.Services/ReadingLists/ReadingListService.cs","lineNumber":79,"sourceCode":"\n        var readingList = new ReadingListBuilder(title).Build();\n        userWithReadingList.ReadingLists.Add(readingList);\n\n        if (!unitOfWork.HasChanges()) throw new KavitaException(\"generic-reading-list-create\");\n        await unitOfWork.CommitAsync();\n        return readingList;\n    }\n\n    /// <summary>\n    ///\n    /// </summary>\n    /// <param name=\"readingList\"></param>\n    /// <param name=\"dto\"></param>\n    /// <exception cref=\"KavitaException\"></exception>\n    public async Task UpdateReadingList(ReadingList readingList, UpdateReadingListDto dto)\n    {\n        dto.Title = dto.Title.Trim();\n        if (string.IsNullOrEmpty(dto.Title)) throw new KavitaException(\"reading-list-title-required\");\n\n        if (!dto.Title.Equals(readingList.Title) && await unitOfWork.ReadingListRepository.ReadingListExists(dto.Title, readingList.Id))\n            throw new KavitaException(\"reading-list-name-exists\");\n\n        readingList.Summary = dto.Summary;\n        readingList.Title = dto.Title.Trim();\n        readingList.NormalizedTitle = Parser.Normalize(readingList.Title);\n        readingList.Promoted = dto.Promoted;\n        readingList.CoverImageLocked = dto.CoverImageLocked;\n\n        if (readingList.Tags == null)\n        {\n            throw new ArgumentException(\"You must pass a Reading List with tags included\");\n        }\n\n        await TagHelper.UpdateEntityTags(readingList.Tags, dto.Tags, unitOfWork.DataContext.ReadingListTag, unitOfWork, false);\n\n","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/ReadingLists/ReadingListService.cs#L61-L97","documentation":"Thrown by ReadingListService.UpdateReadingList after trimming dto.Title, when the result is null or empty. A reading list must always have a non-empty title, so an update that would clear it is rejected before any persistence.","triggerScenarios":"PUT/PATCH update of a reading list where dto.Title is null, empty, or only whitespace (after .Trim() it becomes empty).","commonSituations":"Frontend sent an empty title field; a user cleared the title input; or a bulk update script passed null title to 'reset' it.","solutions":["Require a non-empty title in the client form before enabling save.","Server-side DTO validation: add [Required] / [MinLength(1)] on UpdateReadingListDto.Title.","If the title is optional for the patch, skip the assignment when empty instead of throwing.","Return the localized 'reading-list-title-required' message to the user so they re-enter a name."],"exampleFix":"// before\nawait readingListService.UpdateReadingList(list, dto);\n\n// after - DTO validation\npublic class UpdateReadingListDto\n{\n    [Required] public string Title { get; set; } = string.Empty;\n}\n// controller returns 400 automatically before the service is reached","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(dto?.Title))\n    return BadRequest(\"reading-list-title-required\");\nawait readingListService.UpdateReadingList(list, dto);","typeGuard":"static bool HasNonEmptyTitle(UpdateReadingListDto dto) => !string.IsNullOrWhiteSpace(dto?.Title);","tryCatchPattern":null,"preventionTips":["Add [Required] on UpdateReadingListDto.Title for automatic 400s.","Disable save in the UI when the title field is empty.","Reject empty titles before reaching the service."],"tags":["reading-lists","validation","required-field"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}