Issue 1:
L-BFGS-B does not perform the requested number of iterations for less than n<25 iterations. It also calls the callback an incorrect (and different!) number of iterations.

A sample code,

from itertools import count
from scipy.optimize import minimize, rosen

c = count()
def callback(x):
next(c)

n=25
res = minimize(rosen, (0,0,0), options={'disp': True, 'maxiter': n}, method='L-BFGS-B', callback=callback)
print(res)
print(c)

The behavior is incorrect for n<25, for example n=3 produces

RUNNING THE L-BFGS-B CODE

* * *

Machine precision = 2.220D-16
N = 3 M = 10
This problem is unconstrained.

At X0 0 variables are exactly at the bounds

At iterate 0 f= 2.00000D+00 |proj g|= 2.00000D+00

At iterate 1 f= 1.95917D+00 |proj g|= 2.14559D+00

At iterate 2 f= 1.61222D+00 |proj g|= 4.47056D+00

At iterate 3 f= 1.57410D+00 |proj g|= 6.85382D+00

At iterate 4 f= 1.43040D+00 |proj g|= 6.93083D+00

At iterate 5 f= 1.22860D+00 |proj g|= 1.52933D+01 <= 5 iters

* * *

Tit = total number of iterations
Tnf = total number of function evaluations
Tnint = total number of segments explored during Cauchy searches
Skip = number of BFGS updates skipped
Nact = number of active bounds at final generalized Cauchy point
Projg = norm of the final projected gradient
F = final function value

* * *

N Tit Tnf Tnint Skip Nact Projg F
3 5 12 1 0 0 1.529D+01 1.229D+00
F = 1.22859593979883 <= final F

STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT

Cauchy time 0.000E+00 seconds.
Subspace minimization time 0.000E+00 seconds.
Line search time 0.000E+00 seconds.

Total User time 0.000E+00 seconds.

fun: 1.2285959397988266
hess_inv: <3x3 LbfgsInvHessProduct with dtype=float64>
jac: array([ 10.82081735, -15.2932714 , 5.76619654])
message: b'STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT'
nfev: 48
nit: 4 <= internal profiling only counts 4 iters
status: 1
success: False
x: array([ 0.52583399, 0.22054671, 0.07747183])

count(4) <= callback only called 4 times

Issue 2:
If the optimization does not finish early, L-BFGS-B runs 1 iteration more than requested.

Reproducing code example:

import itertools
from scipy import optimize

def f(x):
x, y = x
return (1 - x)**2 + 100 * (y - x**2)**2 # Rosenbrock function

counter = itertools.count()

def c(x):
print next(counter), x, f(x)

optimize.minimize(f, [0, 0], method='l-bfgs-b', callback=c,
options={'maxiter': 5})

# Output
0 [ 1.74855120e-01 -8.73576809e-08] 0.774343468431
1 [ 0.26113558 0.04906611] 0.582499810584
2 [ 0.32394677 0.11056373] 0.460208907475
3 [ 0.4240506 0.16523825] 0.352977278528
4 [ 0.51688677 0.23667522] 0.326403357767
5 [ 0.52242281 0.26249422] 0.238961333065 <-- This should be the optimized function value
nit: 6 <-- This is already one too many
success: False
message: 'STOP: TOTAL NO. of ITERATIONS EXCEEDS LIMIT'
x: array([ 0.60246124, 0.35628737])
fun: 0.16248886013148453 <-- But this is less than reported in the last callback.
jac: array([ 0.81281454, -1.33443389])
nfev: 30
status: 1

Scipy/Numpy/Python version information:

('0.15.1',
'1.11.1',
sys.version_info(major=2, minor=7, micro=6, releaselevel='final', serial=0))

The wrong logic is here. It is probably enough to increase the n_iterations counter (and call the callback!) before checking it against maxiter. And the check should be a <=, not a <.