HTTP 响应码有哪些?分别代表什么意思?8 年 Java 开发:从踩坑到实战(附 Spring Boot 代码)

106 阅读13分钟

HTTP 响应码有哪些?分别代表什么意思?8 年 Java 开发:从踩坑到实战(附 Spring Boot 代码)

刚入行那年,我写了个用户登录接口,前端反馈 “点登录没反应”。查日志发现接口返回 200,但响应体里藏着 “用户名不存在” 的错误信息 —— 前端以为 200 就是成功,没解析错误内容,用户卡了半天。后来又踩过 “把 401(未登录)返回成 403(权限不足)”“500 直接抛堆栈给用户” 的坑,才明白:HTTP 响应码不是随便填的数字,而是前后端沟通的 “通用语言”,填错了就是 “鸡同鸭讲”

今天就从 8 年 Java 开发的实战角度,把 HTTP 响应码的 “分类逻辑 + 业务场景 + 代码落地 + 避坑指南” 讲透 —— 不管你是写接口还是调接口,看完都能避免 “响应码乱用” 的尴尬。

一、先搞懂:HTTP 响应码为什么分 5 大类?

HTTP 响应码总共分 5 类(1xx-5xx),每类对应 “请求处理的不同阶段”,记住这个逻辑就不会乱:

类别核心含义场景类比开发中遇到的频率
1xx信息性响应(临时通知)外卖下单后 “商家已接单”极少(几乎不用)
2xx成功响应(请求搞定了)外卖 “已送达”最高(天天用)
3xx重定向(需要进一步操作)外卖 “地址不对,引导改地址”中(登录、缓存常用)
4xx客户端错误(你发错了)外卖 “没填收货地址,下不了单”高(参数错、没登录)
5xx服务器错误(我搞砸了)外卖 “商家系统崩溃,下不了单”中(代码 bug、服务挂了)

简单说:2xx 是 “好事”,4xx 是 “你的错”,5xx 是 “我的错”,3xx 是 “再走一步”,1xx 是 “临时通知” 。搞懂这个分类,后面记具体响应码就轻松了。

二、每类关键响应码:业务场景 + Java 代码落地

不是所有响应码都常用,8 年开发下来,高频用到的就 10 来个。每个都附 “业务场景 + Spring Boot 代码示例”,直接复用。

1. 2xx 成功响应:“事儿办成了,这是结果”

核心:客户端请求完全符合预期,服务器成功处理。高频用 3 个:200、201、202。

(1)200 OK:通用成功(最常用)
  • 业务场景:请求成功且返回数据,比如 “查询用户信息”“获取商品列表”“登录成功”。
  • 注意:200 只代表 “请求处理成功”,不代表 “业务成功”—— 比如登录接口,用户名密码正确返回 200+“登录成功”,用户名不存在不能返回 200(应该返回 400 或 401)。
(2)201 Created:创建资源成功
  • 业务场景:客户端触发 “创建资源” 的操作,比如 “创建订单”“新增用户”“上传文件”。
  • 关键:返回 201 时,最好在响应头加Location,告诉客户端新资源的地址(比如新订单的详情地址)。
(3)202 Accepted:异步处理中(少用但重要)
  • 业务场景:请求已接收,但需要异步处理(不能马上返回结果),比如 “提交大数据导出任务”“发起异步对账”。
  • 区别 200:200 是 “已完成”,202 是 “已接收,正在做”。
Spring Boot 代码示例(返回 2xx)
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    // 1. 创建订单:返回201 Created(带新资源地址)
    @PostMapping
    public ResponseEntity<OrderVO> createOrder(@RequestBody OrderDTO orderDTO) {
        // 业务逻辑:创建订单
        OrderVO orderVO = orderService.create(orderDTO);
        
        // 设置响应头:Location指向新订单的详情接口
        HttpHeaders headers = new HttpHeaders();
        headers.add("Location", "/api/orders/" + orderVO.getId());
        
        // 返回201 + 订单信息 + 响应头
        return new ResponseEntity<>(orderVO, headers, HttpStatus.CREATED);
    }

    // 2. 查询订单:返回200 OK
    @GetMapping("/{id}")
    public ResponseEntity<OrderVO> getOrder(@PathVariable Long id) {
        OrderVO orderVO = orderService.getById(id);
        // 200可以省略,ResponseEntity默认用200
        return ResponseEntity.ok(orderVO);
    }

    // 3. 导出订单:返回202 Accepted(异步处理)
    @PostMapping("/export")
    public ResponseEntity<String> exportOrders(@RequestBody ExportDTO exportDTO) {
        // 业务逻辑:提交导出任务,返回任务ID
        String taskId = exportService.submitTask(exportDTO);
        
        // 返回202 + 任务ID(客户端用任务ID查结果)
        return ResponseEntity.status(HttpStatus.ACCEPTED)
                .body("导出任务已接收,任务ID:" + taskId);
    }
}

2. 3xx 重定向:“你得换个地方找结果”

核心:客户端请求的资源 “不在这”,需要进一步操作才能拿到结果。高频用 3 个:301、302、304。

(1)301 Moved Permanently:永久重定向
  • 业务场景:资源 “永久搬家” 了,比如旧域名xxx.com迁到新域名new.xxx.com,旧接口/api/v1/order迁到/api/v2/order
  • 注意:浏览器会缓存 301,下次直接访问新地址,所以确定 “永久迁移” 才用。
(2)302 Found:临时重定向(最常用)
  • 业务场景:资源 “临时在别的地方”,比如 “未登录用户访问需要登录的页面,跳转到登录页”“秒杀活动结束,跳转到活动结束页”。
  • 区别 301:302 是临时的,浏览器不缓存;301 是永久的,浏览器缓存。
(3)304 Not Modified:资源未修改(缓存专用)
  • 业务场景:客户端请求缓存资源时,服务器告诉它 “资源没改,你用本地缓存就行”,比如 “静态资源(CSS/JS/ 图片)”“查询不常变的数据(比如省份列表)”。
  • 原理:客户端请求时带If-Modified-Since(最后修改时间)或If-None-Match(ETag 标识),服务器判断资源没改,返回 304,不返回资源内容(省带宽)。
Spring Boot 代码示例(3xx 重定向)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
@RequestMapping("/api")
public class RedirectController {

    // 1. 302临时重定向:未登录跳登录页
    @GetMapping("/user/profile")
    public RedirectView userProfile(HttpServletRequest request) {
        // 判断是否登录(实际项目用Session/Token)
        Boolean isLogin = (Boolean) request.getSession().getAttribute("isLogin");
        if (isLogin == null || !isLogin) {
            // 302重定向到登录页(默认是302,指定status=301就是永久重定向)
            return new RedirectView("/api/login", true);
        }
        // 已登录,返回用户信息(200)
        return new RedirectView("/api/user/info", true);
    }

    // 2. 304 Not Modified:静态资源缓存
    @GetMapping("/static/provinces")
    public void getProvinces(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 1. 获取客户端带的"If-Modified-Since"头(资源最后修改时间)
        String ifModifiedSince = request.getHeader("If-Modified-Since");
        // 2. 假设省份列表最后修改时间是2024-01-01(实际项目存在数据库/配置中)
        String lastModified = "Mon, 01 Jan 2024 00:00:00 GMT";
        
        // 3. 判断资源是否修改:如果客户端带的时间 >= 最后修改时间,返回304
        if (ifModifiedSince != null && ifModifiedSince.equals(lastModified)) {
            response.setStatus(HttpStatus.NOT_MODIFIED.value());
            return;
        }
        
        // 4. 资源已修改,返回200 + 数据 + 最后修改时间头
        response.setStatus(HttpStatus.OK.value());
        response.setHeader("Last-Modified", lastModified);
        response.getWriter().write("["北京","上海","广州"]");
    }
}

3. 4xx 客户端错误:“你发的请求有问题,我没法处理”

核心:问题出在客户端(比如参数错、没登录、要的资源不存在),服务器没义务处理错误请求。高频用 5 个:400、401、403、404、429。

(1)400 Bad Request:请求参数错误(最常用)
  • 业务场景:客户端提交的参数不符合要求,比如 “下单接口没传商品 ID”“手机号格式不对”“年龄传了字符串”。
  • 避坑:不要把 400 和 404 搞混 ——400 是 “参数错”,404 是 “路径错 / 资源不存在”。
(2)401 Unauthorized:未登录 / 身份未验证
  • 业务场景:客户端访问需要登录的接口,但没传登录凭证(Token/Session),比如 “未登录用户调用下单接口”“Token 过期”。
  • 关键:返回 401 时,最好在响应头加WWW-Authenticate,告诉客户端 “需要怎么登录”(比如 “请传 Bearer Token”)。
(3)403 Forbidden:权限不足(已登录但没权限)
  • 业务场景:客户端已登录,但没有操作资源的权限,比如 “普通用户想修改管理员账号”“员工想查看其他部门的工资”。
  • 区别 401:401 是 “没登录”,403 是 “已登录但没权限”—— 这是新手最容易搞混的两个码!
(4)404 Not Found:资源不存在
  • 业务场景:两种情况:① 请求路径错(比如/api/order写成/api/orders);② 资源真的不存在(比如查 ID=999 的订单,实际没有)。
  • 避坑:不要用 404 返回 “业务不存在”(比如 “用户名不存在”),应该用 400 或自定义业务码 ——404 更适合 “路径错” 或 “物理资源不存在”。
(5)429 Too Many Requests:请求太频繁(接口防刷)
  • 业务场景:客户端短时间内请求次数太多,触发接口防刷,比如 “1 分钟内发 10 次短信验证码”“每秒刷 100 次下单接口”。
  • 关键:返回 429 时,加Retry-After头告诉客户端 “多久后能再请求”(比如Retry-After: 60表示 1 分钟后)。
Spring Boot 代码示例(4xx 全局异常处理)

@ControllerAdvice统一处理客户端错误,避免每个接口都写重复的响应码逻辑:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

// 全局异常处理器:统一返回4xx响应码
@ControllerAdvice
public class GlobalExceptionHandler {

    // 1. 400 Bad Request:参数校验失败(@Valid注解触发)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<Map<String, String>> handleParamError(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> errorMap = new HashMap<>();
        // 收集所有参数错误(比如“productId:商品ID不能为空”)
        bindingResult.getFieldErrors().forEach(fieldError -> 
            errorMap.put(fieldError.getField(), fieldError.getDefaultMessage())
        );
        return new ResponseEntity<>(errorMap, HttpStatus.BAD_REQUEST);
    }

    // 2. 401 Unauthorized:未登录异常(自定义异常)
    @ExceptionHandler(NotLoginException.class)
    public void handleNotLogin(NotLoginException e, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        // 加WWW-Authenticate头,告诉客户端需要传Token
        response.setHeader("WWW-Authenticate", "Bearer realm="请登录获取Token"");
        response.getWriter().write("{"code":401,"msg":"" + e.getMessage() + ""}");
    }

    // 3. 403 Forbidden:权限不足异常(自定义异常)
    @ExceptionHandler(NoPermissionException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ResponseEntity<Map<String, String>> handleNoPermission(NoPermissionException e) {
        Map<String, String> result = new HashMap<>();
        result.put("code", "403");
        result.put("msg", e.getMessage());
        return new ResponseEntity<>(result, HttpStatus.FORBIDDEN);
    }

    // 4. 429 Too Many Requests:请求太频繁(自定义异常)
    @ExceptionHandler(TooManyRequestsException.class)
    public ResponseEntity<Map<String, String>> handleTooManyRequests(TooManyRequestsException e) {
        Map<String, String> result = new HashMap<>();
        result.put("code", "429");
        result.put("msg", e.getMessage());
        // 加Retry-After头:1分钟后可重试
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                .header("Retry-After", "60")
                .body(result);
    }
}

// 自定义异常(实际项目放单独的exception包)
class NotLoginException extends RuntimeException {
    public NotLoginException(String message) {
        super(message);
    }
}

class NoPermissionException extends RuntimeException {
    public NoPermissionException(String message) {
        super(message);
    }
}

class TooManyRequestsException extends RuntimeException {
    public TooManyRequestsException(String message) {
        super(message);
    }
}

4. 5xx 服务器错误:“你请求没问题,但我这边出故障了”

核心:客户端请求是对的,但服务器内部出错(代码 bug、服务挂了、依赖超时) ,这时候要 “藏好错误细节,给用户友好提示”。高频用 3 个:500、502、503。

(1)500 Internal Server Error:通用服务器错误(最常见)
  • 业务场景:服务器代码抛异常(比如空指针、数据库报错),比如 “下单接口查询库存时数据库连接超时”“代码里写了1/0”。
  • 避坑:绝对不能把堆栈信息返回给客户端!要统一捕获异常,返回 “服务器内部错误,请稍后重试”,同时把堆栈记到日志里(方便排查)。
(2)502 Bad Gateway:网关错误
  • 业务场景:微服务架构中,客户端请求经过网关(比如 Nginx、Spring Cloud Gateway),但网关转发请求时,后端服务没响应或返回错误,比如 “网关调用订单服务时,订单服务宕机了”。
  • 责任方:问题在 “网关和后端服务之间”,客户端和网关的通信是好的。
(3)503 Service Unavailable:服务不可用(临时)
  • 业务场景:服务器暂时没法处理请求(不是永久挂了),比如 “服务在重启”“服务触发降级(比如秒杀时流量太大,暂时关闭非核心接口)”“服务器 CPU / 内存满了”。
  • 关键:返回 503 时,加Retry-After头告诉客户端 “多久后重试”,比如服务重启需要 10 秒,就加Retry-After: 10
Spring Boot 代码示例(500 统一处理)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class ServerExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(ServerExceptionHandler.class);

    // 500 Internal Server Error:捕获所有未处理的异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, String>> handleServerError(Exception e) {
        // 1. 把错误堆栈记到日志(排查问题的关键!)
        log.error("服务器内部错误", e);
        
        // 2. 返回友好提示,不暴露堆栈
        Map<String, String> result = new HashMap<>();
        result.put("code", "500");
        result.put("msg", "服务器内部错误,请稍后重试");
        
        return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // 503 Service Unavailable:服务降级时返回
    @ExceptionHandler(ServiceDegradeException.class)
    public ResponseEntity<Map<String, String>> handleServiceDegrade(ServiceDegradeException e) {
        Map<String, String> result = new HashMap<>();
        result.put("code", "503");
        result.put("msg", e.getMessage());
        
        // 告诉客户端10秒后重试
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .header("Retry-After", "10")
                .body(result);
    }
}

// 自定义服务降级异常
class ServiceDegradeException extends RuntimeException {
    public ServiceDegradeException(String message) {
        super(message);
    }
}

三、8 年开发的避坑指南:这些响应码别乱用!

  1. 坑 1:用 200 返回所有结果,包括错误
    比如登录接口 “用户名不存在” 返回 200+“错误信息”—— 前端得解析响应体才知道错了,不符合 HTTP 规范。正确做法:返回 400(参数错误)或自定义业务码(200 里带success: false)。
  2. 坑 2:401 和 403 搞混
    未登录返回 403,已登录没权限返回 401—— 前端会误以为 “没权限是没登录”,引导用户重复登录。记住:401 是 “没身份”,403 是 “有身份但没权限”
  3. 坑 3:500 直接抛堆栈给客户端
    曾经有个项目把空指针堆栈返回给用户,用户截图发朋友圈吐槽 “这系统太烂了”。正确做法:日志记堆栈,给用户返回 “服务器内部错误,请稍后重试”。
  4. 坑 4:用 404 返回 “业务不存在”
    比如 “查询订单 ID=999 不存在” 返回 404——404 更适合 “路径错了”(比如/api/order写成/api/orders)。业务不存在建议返回 200+data: null或 400+“订单不存在”。
  5. 坑 5:忽略 304 的缓存作用
    静态资源(CSS/JS/ 图片)每次都返回 200,浪费带宽。正确做法:用 304+Last-Modified/ETag,让客户端复用缓存。

四、总结:响应码的 “黄金法则”

8 年开发下来,我总结出 3 条响应码使用的黄金法则,帮你少踩 90% 的坑:

  1. “对号入座” :按 “请求处理阶段” 选类别(2xx 成功、4xx 客户端错、5xx 服务器错),再选具体码;

  2. “前后端共识” :和前端约定清楚每个码的含义(比如 401 代表 “Token 过期,需要重新登录”),避免歧义;

  3. “隐藏细节,暴露必要信息” :客户端错(4xx)要告诉 “错在哪”(比如 “手机号格式不对”),服务器错(5xx)要隐藏细节(比如不抛堆栈),重定向(3xx)要告诉 “去哪”。

其实 HTTP 响应码不难,难的是 “养成规范使用的习惯”。下次写接口时,别再随手填 200 或 500,多想想 “这个场景该用哪个码,前端拿到后会怎么处理”—— 这才是专业开发的体现。

如果你的项目里还有 “响应码乱用” 的问题,赶紧用文中的全局异常处理器优化下吧~ 有问题欢迎评论区交流!