Kareadita/Kavita · error · KavitaException

reading-list-title-required

reading-list-title-required

Error message

reading-list-title-required

What it means

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.

Source

Thrown at Kavita.Services/ReadingLists/ReadingListService.cs:79

        var readingList = new ReadingListBuilder(title).Build();
        userWithReadingList.ReadingLists.Add(readingList);

        if (!unitOfWork.HasChanges()) throw new KavitaException("generic-reading-list-create");
        await unitOfWork.CommitAsync();
        return readingList;
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="readingList"></param>
    /// <param name="dto"></param>
    /// <exception cref="KavitaException"></exception>
    public async Task UpdateReadingList(ReadingList readingList, UpdateReadingListDto dto)
    {
        dto.Title = dto.Title.Trim();
        if (string.IsNullOrEmpty(dto.Title)) throw new KavitaException("reading-list-title-required");

        if (!dto.Title.Equals(readingList.Title) && await unitOfWork.ReadingListRepository.ReadingListExists(dto.Title, readingList.Id))
            throw new KavitaException("reading-list-name-exists");

        readingList.Summary = dto.Summary;
        readingList.Title = dto.Title.Trim();
        readingList.NormalizedTitle = Parser.Normalize(readingList.Title);
        readingList.Promoted = dto.Promoted;
        readingList.CoverImageLocked = dto.CoverImageLocked;

        if (readingList.Tags == null)
        {
            throw new ArgumentException("You must pass a Reading List with tags included");
        }

        await TagHelper.UpdateEntityTags(readingList.Tags, dto.Tags, unitOfWork.DataContext.ReadingListTag, unitOfWork, false);

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Require a non-empty title in the client form before enabling save.
  2. Server-side DTO validation: add [Required] / [MinLength(1)] on UpdateReadingListDto.Title.
  3. If the title is optional for the patch, skip the assignment when empty instead of throwing.
  4. Return the localized 'reading-list-title-required' message to the user so they re-enter a name.

Example fix

// before
await readingListService.UpdateReadingList(list, dto);

// after - DTO validation
public class UpdateReadingListDto
{
    [Required] public string Title { get; set; } = string.Empty;
}
// controller returns 400 automatically before the service is reached
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(dto?.Title))
    return BadRequest("reading-list-title-required");
await readingListService.UpdateReadingList(list, dto);

Type guard

static bool HasNonEmptyTitle(UpdateReadingListDto dto) => !string.IsNullOrWhiteSpace(dto?.Title);

Prevention

When it happens

Trigger: PUT/PATCH update of a reading list where dto.Title is null, empty, or only whitespace (after .Trim() it becomes empty).

Common situations: Frontend sent an empty title field; a user cleared the title input; or a bulk update script passed null title to 'reset' it.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/440beee58fe07947. Report an issue: GitHub.