Comment connecter un backend Java à un frontend HTML/CSS

Un backend Java et un frontend HTML/CSS sont deux mondes distincts qui communiquent via HTTP. L'architecture moderne la plus répandue est une API REST en Java qui sert du JSON, consommée par du JavaScript dans la page web. Voici un parcours complet de cette intégration.

Vue d'ensemble

[Navigateur]                [Serveur]

HTML + CSS + JS  ←── HTTP ──→  Backend Java
(Chrome/Firefox)              (Spring Boot, Tomcat, Quarkus…)
                                    │
                                    ├── Base de données
                                    └── Services externes

Le navigateur télécharge HTML + CSS + JS, puis le JavaScript interroge l'API Java pour récupérer ou envoyer des données.

Backend Spring Boot minimal

Un contrôleur REST qui retourne du JSON :

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final List<User> users = new ArrayList<>(List.of(
        new User(1, "Alice"),
        new User(2, "Bob")
    ));

    @GetMapping
    public List<User> all() { return users; }

    @GetMapping("/{id}")
    public User one(@PathVariable int id) {
        return users.stream().filter(u -> u.id() == id).findFirst()
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    @PostMapping
    public User create(@RequestBody User user) {
        users.add(user);
        return user;
    }

    public record User(int id, String name) { }
}

Lancé avec mvn spring-boot:run, le service répond sur http://localhost:8080/api/users.

Frontend HTML/JavaScript

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Users</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <h1>Liste des utilisateurs</h1>
  <ul id="users"></ul>
  <form id="addForm">
    <input name="name" placeholder="Nom" required>
    <button type="submit">Ajouter</button>
  </form>

  <script>
    async function load() {
      const res = await fetch('/api/users');
      const list = await res.json();
      document.getElementById('users').innerHTML =
        list.map(u => `<li>${u.id} – ${u.name}</li>`).join('');
    }

    document.getElementById('addForm').addEventListener('submit', async (e) => {
      e.preventDefault();
      const name = new FormData(e.target).get('name');
      const res = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ id: Date.now(), name })
      });
      if (res.ok) { e.target.reset(); load(); }
    });

    load();
  </script>
</body>
</html>

Option A — Servir le frontend depuis le backend

Placer les fichiers statiques dans src/main/resources/static/. Spring Boot les expose automatiquement à la racine. Pas de problème de CORS puisque tout est servi depuis la même origine.

src/
└── main/
    └── resources/
        ├── static/
        │   ├── index.html
        │   ├── styles.css
        │   └── app.js
        └── application.properties

Option B — Frontend et backend séparés (CORS requis)

Si le frontend est servi par Vite/Webpack sur le port 3000 et le backend sur 8080, il faut autoriser les requêtes cross-origin :

@RestController
@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping("/api/users")
public class UserController { ... }

Ou plus global :

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://localhost:3000", "https://myapp.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowCredentials(true);
    }
}

Sans Spring — avec un simple servlet

Pour du Tomcat pur, sans framework :

@WebServlet("/api/users")
public class UserServlet extends HttpServlet {
    private static final ObjectMapper JSON = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("application/json");
        resp.setCharacterEncoding("UTF-8");
        JSON.writeValue(resp.getWriter(), List.of(
            Map.of("id", 1, "name", "Alice"),
            Map.of("id", 2, "name", "Bob")
        ));
    }
}

Les formes moins modernes

JSP — HTML généré côté serveur

Le serveur injecte directement les données dans la page. Simple pour des sites classiques, mais couple fortement front et back.

<%@ page contentType="text/html; charset=UTF-8" %>
<html><body>
  <ul>
    <% for (User u : (List<User>) request.getAttribute("users")) { %>
      <li><%= u.getName() %></li>
    <% } %>
  </ul>
</body></html>

Thymeleaf — la version moderne

Syntaxe plus propre que JSP, intégrée à Spring Boot. Recommandée si vous voulez du rendu serveur sans écrire de JavaScript.

Bonnes pratiques

  1. Toujours parser le JSON côté client (JSON.parse ou response.json()) — jamais eval().
  2. Valider côté serveur — le client n'est jamais digne de confiance.
  3. HTTPS en production — obligatoire dès qu'il y a authentification.
  4. Limiter CORS aux origines connues — jamais * avec credentials.
  5. Retourner des erreurs JSON structurées ({error, message}) plutôt que du HTML.
  6. Compresser les réponses : Spring Boot supporte gzip via server.compression.enabled=true.

Architecture recommandée

Pour un nouveau projet :

  • Backend : Spring Boot 3 + Spring Web + Spring Data JPA.
  • Frontend : HTML/CSS statique + JavaScript vanilla pour les cas simples, React/Vue pour les interfaces riches.
  • Communication : API REST en JSON, authentification par JWT ou cookie de session.
  • Déploiement : un WAR ou un JAR exécutable contenant les deux (Spring Boot static/) ou deux services séparés derrière un reverse proxy.

Avec cette base, le frontend et le backend évoluent à leur rythme propre, connectés par un contrat HTTP clair.