В чём разница между библиотекой, утилитой и компонентом

Библиотека — это набор классов, который позволяет выполнить полезные действия путём обращения к API этого набора.
Программная утилита — это библиотека + точка запуска, которая выполняет полезные действия путём обращения к API этой библиотеки.
Компонент — это библиотека + точка запуска, которая экспонирует API этой библиотеки.

Под программной утилитой здесь подразумевается не утилитарный java-класс, а программа, которая обычно вызывается в консоли. Примером такой утилиты может быть mvn.

Ниже рассмотрим три примера.

Библиотека:

public class Dice {

    public int roll() {
        return (int) (Math.random() * 6 + 1);
    }
}

Библиотека состоит из одного класса Dice и позволяет бросать кубик. Метод roll составляет API библиотеки. Точка запуска, то есть метод main, отсутствует.

Программная утилита:

public class Launcher {

    public static void main(String[] args) {
        var dice = new Dice();
        System.out.print(dice.roll());
    }
}
public class Dice {

    public int roll() {
        return (int) (Math.random() * 6 + 1);
    }
}

Класс Launcher содержит точку запуска — метод main, — которая обращается к API библиотеки, то есть к методу roll, и выводит результат в консоль. В отличие от библиотеки, которая предполагает только встраивание её в какой-то другой код, утилита предполагает наличие клиента — человека или другой программы, — который вызывает её ради получения результата.

Вызвать такую утилиту можно из консоли:

java -cp . Launcher

Чтобы бросить кубик ещё раз, утилиту нужно запускать заново.

Компонент:

import java.util.Scanner;

public class Launcher {

    public static void main(String[] args) {
        var dice = new Dice();
        var in = new Scanner(System.in);
        var out = System.out;
        while (in.hasNextLine()) {
            in.nextLine();
            out.print(dice.roll());  // Exposing method `roll`
        }
    }
}
public class Dice {

    public int roll() {
        return (int) (Math.random() * 6 + 1);
    }
}

Здесь точка запуска не вызывает API ради разового результата, а экспонирует его: программа разворачивается в памяти и делает бросок кубика доступным через стандартный протокол ввода-вывода. После запуска бросать кубик можно сколько угодно раз.

API библиотеки можно выставить и через транспортный протокол:

import java.util.Scanner;
import java.net.ServerSocket;
import java.io.PrintWriter;
import java.io.IOException;

public class Launcher {

    public static void main(String[] args) throws IOException {
        var server = new ServerSocket(5000);                        // Exposing the facade
        var client = server.accept();
        var dice = new Dice();
        var in = new Scanner(client.getInputStream());              // Instead of System.in
        var out = new PrintWriter(client.getOutputStream(), true);  // Instead of System.out
        while (in.hasNextLine()) {
            in.nextLine();
            out.print(dice.roll());
        }
    }
}

Код намеренно упрощён: за весь жизненный цикл к серверу может подключиться только один клиент, но в рамках этого подключения он может бросать кубик сколько угодно раз.

Опираясь на изложенную разницу, можно дать следующее определение компоненту. Компонент — это библиотека, у которой есть точка запуска, через которую другие компоненты могут получить доступ к её возможностям.